@revenexx/studio-integrations 3.8.4 → 3.8.5

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @revenexx/studio-integrations
2
2
 
3
+ ## 3.8.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 2c66daf: A run's record is now headed by the workflow and "Run #" with the run's number, instead of the identifier the engine gave the run. The engine's own fields — history length, workflow and run identifiers, workflow type, task queue — and, for a run still going, the stack trace now sit under "Technical details", folded until opened. The two ways back from a failed run are one header button, "Run again" ("Erneut ausführen"), that opens "Resume from the failed step" ("Ab fehlgeschlagenem Schritt fortsetzen") and "Run again from the start" ("Komplett neu ausführen"); the resume no longer stands on its own above the record, and a run row's menu uses the same two names. New message keys `integration.editor.action.rerun` and `integration.runs.detail.technical.title`; `integration.runs.confirm.resume` and `integration.runs.notice.started` are gone with the record's own resume.
8
+
3
9
  ## 3.8.4
4
10
 
5
11
  ### Patch Changes
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@revenexx/studio-integrations",
3
3
  "configKey": "studioIntegrations",
4
- "version": "3.8.4",
4
+ "version": "3.8.5",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "unknown"
@@ -102,7 +102,7 @@ function confirmStop(a) {
102
102
  <MoreHorizontal :size="16" />
103
103
  </Button>
104
104
  </DropdownMenuTrigger>
105
- <DropdownMenuContent align="end" class="w-40" @click.stop>
105
+ <DropdownMenuContent align="end" class="w-64" @click.stop>
106
106
  <DropdownMenuItem @select="viewDetails">{{ t("integration.runs.menu.viewDetails") }}</DropdownMenuItem>
107
107
  <DropdownMenuItem @select="viewReplay">{{ t("integration.runs.menu.replay") }}</DropdownMenuItem>
108
108
  <DropdownMenuSeparator v-if="hasAny" />
@@ -9,7 +9,6 @@ import { IntegrationsApiError, errorReason } from "../../utils/apiError";
9
9
  import { createPollBudget, SAFETY_POLL_MS } from "../../utils/pollBudget";
10
10
  import { formatDuration } from "../../utils/formatDuration";
11
11
  import { unwrapRun } from "../../utils/runEnvelope";
12
- import { acceptsResume } from "../../utils/runFeedQuery";
13
12
  import { splitRunOutputs } from "../../utils/runOutputs";
14
13
  import { formatDateTime, formatTime } from "../../utils/formatDateTime";
15
14
  const props = defineProps({
@@ -26,8 +25,7 @@ const {
26
25
  getHistory,
27
26
  getStackTrace,
28
27
  cancelRun,
29
- terminateRun,
30
- resumeRun
28
+ terminateRun
31
29
  } = useIntegrationsRuns();
32
30
  const DETAILS_POLL_MS = 3e3;
33
31
  const HISTORY_POLL_MS = 1e4;
@@ -493,8 +491,6 @@ function actionLabel(a) {
493
491
  return t("integration.runs.action.cancel");
494
492
  case "terminate":
495
493
  return t("integration.runs.action.terminate");
496
- case "resume":
497
- return t("integration.runs.action.resume");
498
494
  }
499
495
  }
500
496
  function confirmQuestion(a) {
@@ -503,8 +499,6 @@ function confirmQuestion(a) {
503
499
  return t("integration.runs.confirm.cancel");
504
500
  case "terminate":
505
501
  return t("integration.runs.confirm.terminate");
506
- case "resume":
507
- return t("integration.runs.confirm.resume");
508
502
  }
509
503
  }
510
504
  function actionErrorFallback(a) {
@@ -513,17 +507,12 @@ function actionErrorFallback(a) {
513
507
  return t("integration.runs.error.cancel");
514
508
  case "terminate":
515
509
  return t("integration.runs.error.terminate");
516
- case "resume":
517
- return t("integration.runs.error.resume");
518
510
  }
519
511
  }
520
512
  const apiStatusRaw = computed(() => pickStr(runData.value, ["status"]));
521
513
  const isActive = computed(() => !isTerminal.value);
522
514
  const canStop = computed(() => isActive.value && hasTemporal.value);
523
- const canResume = computed(() => {
524
- return acceptsResume(apiStatusRaw.value || props.run.apiStatus, props.run.status) && hasTemporal.value;
525
- });
526
- const hasActions = computed(() => canStop.value || canResume.value);
515
+ const hasActions = computed(() => canStop.value);
527
516
  const armed = ref(null);
528
517
  const actionBusy = ref(false);
529
518
  const actionError = ref(null);
@@ -552,20 +541,15 @@ async function confirmAction() {
552
541
  actionBusy.value = true;
553
542
  actionError.value = null;
554
543
  try {
555
- let res = null;
556
544
  switch (a) {
557
545
  case "cancel":
558
- res = await cancelRun(props.run.workflowId, props.run.id);
546
+ await cancelRun(props.run.workflowId, props.run.id);
559
547
  break;
560
548
  case "terminate":
561
- res = await terminateRun(props.run.workflowId, props.run.id);
562
- break;
563
- case "resume":
564
- res = await resumeRun(props.run.workflowId, props.run.id);
549
+ await terminateRun(props.run.workflowId, props.run.id);
565
550
  break;
566
551
  }
567
- const newId = pickStr(res, ["id"]);
568
- actionNotice.value = a === "resume" && newId && newId !== String(props.run.id) ? t("integration.runs.notice.started", { id: newId }) : t("integration.runs.notice.requested", { action: actionLabel(a) });
552
+ actionNotice.value = t("integration.runs.notice.requested", { action: actionLabel(a) });
569
553
  armed.value = null;
570
554
  await loadAll();
571
555
  startPollingIfNeeded();
@@ -582,8 +566,8 @@ async function confirmAction() {
582
566
  <div class="space-y-4 text-sm">
583
567
  <!-- ── Details view: overview strip + result. ─────────────────────────── -->
584
568
  <template v-if="view === 'details'">
585
- <!-- Toolbar — only the status-gated stop/resume actions; hidden entirely
586
- for a terminal run (Run again sits in the page header, data auto-polls). -->
569
+ <!-- Toolbar — the two stops of a run still going; hidden entirely for one
570
+ that has ended (its ways back are the header's menu, data auto-polls). -->
587
571
  <div v-if="hasActions" class="flex items-center gap-2">
588
572
  <!-- confirm step: one click arms, second confirms -->
589
573
  <template v-if="armed">
@@ -602,7 +586,6 @@ async function confirmAction() {
602
586
  <template v-else>
603
587
  <Button v-if="canStop" variant="outline" size="sm" @click="arm('cancel')">{{ actionLabel("cancel") }}</Button>
604
588
  <Button v-if="canStop" variant="outline" size="sm" class="text-rose-600 hover:bg-rose-500/10" @click="arm('terminate')">{{ actionLabel("terminate") }}</Button>
605
- <Button v-if="canResume" variant="outline" size="sm" @click="arm('resume')">{{ actionLabel("resume") }}</Button>
606
589
  </template>
607
590
  </div>
608
591
 
@@ -614,7 +597,8 @@ async function confirmAction() {
614
597
  {{ actionNotice }}
615
598
  </div>
616
599
 
617
- <!-- Overview — summary + the Temporal facts merged into one strip. -->
600
+ <!-- Overview — how the run ended, when, and why it failed. What the engine
601
+ calls the run lives under Technical details below (PO-692). -->
618
602
  <section class="rounded-md border border-black/[0.06] bg-white p-4 space-y-3">
619
603
  <div class="grid gap-x-6 gap-y-3 sm:grid-cols-2 xl:grid-cols-4">
620
604
  <div>
@@ -633,26 +617,6 @@ async function confirmAction() {
633
617
  <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.completed") }}</div>
634
618
  <div class="tabular-nums">{{ formatDateTime(completedAt) }}</div>
635
619
  </div>
636
- <div>
637
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.historyLength") }}</div>
638
- <div class="tabular-nums">{{ dHistoryLength ?? "\u2014" }}</div>
639
- </div>
640
- <div class="min-w-0">
641
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.workflow") }}</div>
642
- <div class="truncate font-mono text-xs leading-5" :title="temporalWorkflowId ?? '\u2014'">{{ temporalWorkflowId ?? "\u2014" }}</div>
643
- </div>
644
- <div class="min-w-0">
645
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.runId") }}</div>
646
- <div class="truncate font-mono text-xs leading-5" :title="temporalRunId ?? '\u2014'">{{ temporalRunId ?? "\u2014" }}</div>
647
- </div>
648
- <div class="min-w-0">
649
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.workflowType") }}</div>
650
- <div class="truncate font-mono text-xs leading-5">{{ dWorkflowType ?? "\u2014" }}</div>
651
- </div>
652
- <div class="min-w-0">
653
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.taskQueue") }}</div>
654
- <div class="truncate font-mono text-xs leading-5">{{ dTaskQueue ?? "\u2014" }}</div>
655
- </div>
656
620
  </div>
657
621
  <div v-if="errorMessage" class="rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-rose-700">
658
622
  {{ errorMessage }}
@@ -693,28 +657,64 @@ async function confirmAction() {
693
657
  <pre v-else class="rounded-md bg-muted/40 p-3 text-xs overflow-x-auto max-h-[28rem]">{{ json(runResult) }}</pre>
694
658
  </section>
695
659
 
696
- <!-- Stack Trace (non-terminal runs only, lazy) -->
697
- <section v-if="!isTerminal" class="rounded-md border border-black/[0.06] bg-white p-4 space-y-3">
698
- <div class="flex items-center gap-2 border-b border-black/[0.06] pb-2">
699
- <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.stackTrace.title") }}</h4>
700
- <div class="flex-1" />
701
- <Button variant="outline" size="sm" :disabled="stackLoading" @click="loadStack">
702
- {{ stackEnabled ? t("integration.runs.detail.stackTrace.action.reload") : t("integration.runs.detail.stackTrace.action.load") }}
703
- </Button>
704
- </div>
660
+ <!-- Technical details — what the engine calls this run, and the live call
661
+ stack of a run still going. Written for whoever operates the engine, not
662
+ for whoever reads why a run failed, so it starts folded (PO-692). The
663
+ stack trace stays a running run's only: it asks the worker where the run
664
+ is now, and a run that has ended is nowhere. -->
665
+ <details class="group rounded-md border border-black/[0.06] bg-white" data-testid="run-technical-details">
666
+ <summary class="flex cursor-pointer select-none items-center gap-2 px-4 py-3 font-mono text-xs uppercase tracking-wide text-muted-foreground">
667
+ <span aria-hidden="true" class="text-[10px] transition-transform group-open:rotate-90">▶</span>
668
+ <span>{{ t("integration.runs.detail.technical.title") }}</span>
669
+ </summary>
670
+ <div class="space-y-3 px-4 pb-4">
671
+ <div class="grid gap-x-6 gap-y-3 sm:grid-cols-2 xl:grid-cols-4">
672
+ <div>
673
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.historyLength") }}</div>
674
+ <div class="tabular-nums">{{ dHistoryLength ?? "\u2014" }}</div>
675
+ </div>
676
+ <div class="min-w-0">
677
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.workflow") }}</div>
678
+ <div class="truncate font-mono text-xs leading-5" :title="temporalWorkflowId ?? '\u2014'">{{ temporalWorkflowId ?? "\u2014" }}</div>
679
+ </div>
680
+ <div class="min-w-0">
681
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.runId") }}</div>
682
+ <div class="truncate font-mono text-xs leading-5" :title="temporalRunId ?? '\u2014'">{{ temporalRunId ?? "\u2014" }}</div>
683
+ </div>
684
+ <div class="min-w-0">
685
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.workflowType") }}</div>
686
+ <div class="truncate font-mono text-xs leading-5">{{ dWorkflowType ?? "\u2014" }}</div>
687
+ </div>
688
+ <div class="min-w-0">
689
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.taskQueue") }}</div>
690
+ <div class="truncate font-mono text-xs leading-5">{{ dTaskQueue ?? "\u2014" }}</div>
691
+ </div>
692
+ </div>
693
+
694
+ <!-- Stack trace (non-terminal runs only, lazy) -->
695
+ <section v-if="!isTerminal" class="space-y-3 border-t border-black/[0.06] pt-3">
696
+ <div class="flex items-center gap-2">
697
+ <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.stackTrace.title") }}</h4>
698
+ <div class="flex-1" />
699
+ <Button variant="outline" size="sm" :disabled="stackLoading" @click="loadStack">
700
+ {{ stackEnabled ? t("integration.runs.detail.stackTrace.action.reload") : t("integration.runs.detail.stackTrace.action.load") }}
701
+ </Button>
702
+ </div>
705
703
 
706
- <p v-if="!stackEnabled" class="text-xs text-muted-foreground">
707
- {{ t("integration.runs.detail.stackTrace.hint") }}
708
- </p>
709
- <div v-else-if="stackLoading" class="flex items-center gap-2 text-muted-foreground">
710
- <RefreshIcon :size="14" class="animate-spin" /> {{ t("integration.runs.detail.stackTrace.loading") }}
704
+ <p v-if="!stackEnabled" class="text-xs text-muted-foreground">
705
+ {{ t("integration.runs.detail.stackTrace.hint") }}
706
+ </p>
707
+ <div v-else-if="stackLoading" class="flex items-center gap-2 text-muted-foreground">
708
+ <RefreshIcon :size="14" class="animate-spin" /> {{ t("integration.runs.detail.stackTrace.loading") }}
709
+ </div>
710
+ <p v-else-if="stackError" class="text-rose-700">{{ stackError }}</p>
711
+ <pre
712
+ v-else-if="stackTrace"
713
+ class="rounded-md bg-slate-900 text-slate-100 p-3 text-xs overflow-auto max-h-56 whitespace-pre-wrap leading-relaxed"
714
+ >{{ stackTrace }}</pre>
715
+ </section>
711
716
  </div>
712
- <p v-else-if="stackError" class="text-rose-700">{{ stackError }}</p>
713
- <pre
714
- v-else-if="stackTrace"
715
- class="rounded-md bg-slate-900 text-slate-100 p-3 text-xs overflow-auto max-h-56 whitespace-pre-wrap leading-relaxed"
716
- >{{ stackTrace }}</pre>
717
- </section>
717
+ </details>
718
718
  </template>
719
719
 
720
720
  <!-- ── Timeline view: steps + the stacked history. ─────────────────────── -->
@@ -387,8 +387,9 @@
387
387
  "integration.editor.action.more.aria": "Weitere Aktionen für diesen Workflow",
388
388
  "integration.editor.action.more.label": "Aktionen",
389
389
  "integration.editor.action.pause.title": "Workflow pausieren",
390
- "integration.editor.action.retry": "Erneut ausführen",
391
- "integration.editor.action.retry.title": "Führt den Workflow in seinem aktuell gespeicherten Stand aus, mit dem Trigger-Payload dieses Runs.",
390
+ "integration.editor.action.rerun": "Erneut ausführen",
391
+ "integration.editor.action.retry": "Komplett neu ausführen",
392
+ "integration.editor.action.retry.title": "Alle Schritte laufen erneut, auch die bereits erfolgreichen – E-Mails werden zum Beispiel noch einmal gesendet. Verwendet den aktuell gespeicherten Stand des Workflows, mit dem Trigger-Payload dieses Runs.",
392
393
  "integration.editor.action.save.label": "Speichern",
393
394
  "integration.editor.action.save.saving": "Wird gespeichert…",
394
395
  "integration.editor.action.state.label": "Workflow-Status",
@@ -953,10 +954,9 @@
953
954
  "integration.revisions.error.loadFailed": "Der Versionsverlauf konnte nicht geladen werden.",
954
955
  "integration.runs.action.back": "Zurück",
955
956
  "integration.runs.action.cancel": "Run stoppen",
956
- "integration.runs.action.resume": "Fortsetzen",
957
+ "integration.runs.action.resume": "Ab fehlgeschlagenem Schritt fortsetzen",
957
958
  "integration.runs.action.terminate": "Terminieren",
958
959
  "integration.runs.confirm.cancel": "Diesen Run stoppen?",
959
- "integration.runs.confirm.resume": "Diesen Run fortsetzen?",
960
960
  "integration.runs.confirm.terminate": "Diesen Run terminieren?",
961
961
  "integration.runs.detail.error.details.expired": "Verlauf ist nicht mehr verfügbar (Aufbewahrung abgelaufen, kein Snapshot vorhanden).",
962
962
  "integration.runs.detail.error.details.generic": "Temporal-Details konnten nicht geladen werden.",
@@ -996,6 +996,7 @@
996
996
  "integration.runs.detail.steps.loading": "Schritte werden geladen…",
997
997
  "integration.runs.detail.steps.payloadsLoading": "Eingabe und Ausgabe dieses Schritts werden geladen…",
998
998
  "integration.runs.detail.steps.title": "Schritte",
999
+ "integration.runs.detail.technical.title": "Technische Details",
999
1000
  "integration.runs.detail.timeline.label.activity": "Aktivität",
1000
1001
  "integration.runs.detail.timeline.label.timer": "Timer",
1001
1002
  "integration.runs.detail.timeline.label.workflow": "Workflow",
@@ -1011,12 +1012,11 @@
1011
1012
  "integration.runs.menu.aria.actions": "Aktionen für Run #{id}",
1012
1013
  "integration.runs.menu.cancel": "Run stoppen",
1013
1014
  "integration.runs.menu.replay": "Wiedergabe",
1014
- "integration.runs.menu.resume": "Fortsetzen",
1015
- "integration.runs.menu.retry": "Erneut ausführen",
1015
+ "integration.runs.menu.resume": "Ab fehlgeschlagenem Schritt fortsetzen",
1016
+ "integration.runs.menu.retry": "Komplett neu ausführen",
1016
1017
  "integration.runs.menu.terminate": "Terminieren",
1017
1018
  "integration.runs.menu.viewDetails": "Details anzeigen",
1018
1019
  "integration.runs.notice.requested": "{action} angefordert.",
1019
- "integration.runs.notice.started": "Run #{id} gestartet.",
1020
1020
  "integration.runs.payload.expertToggle.label": "Experte (JSON)",
1021
1021
  "integration.runs.payload.extrasNote": "Zusätzliche Payload-Schlüssel aus der Expertenansicht bleiben erhalten: {keys}",
1022
1022
  "integration.runs.payload.file.dropHint": "Datei hier ablegen oder klicken zum Auswählen",
@@ -387,8 +387,9 @@
387
387
  "integration.editor.action.more.aria": "More actions for this workflow",
388
388
  "integration.editor.action.more.label": "Actions",
389
389
  "integration.editor.action.pause.title": "Pause this workflow",
390
- "integration.editor.action.retry": "Run again",
391
- "integration.editor.action.retry.title": "Runs the workflow as it is saved now, with this run's trigger payload.",
390
+ "integration.editor.action.rerun": "Run again",
391
+ "integration.editor.action.retry": "Run again from the start",
392
+ "integration.editor.action.retry.title": "Every step runs again, including those that already succeeded — e-mails are sent again, for example. Uses the workflow as it is saved now, with this run's trigger payload.",
392
393
  "integration.editor.action.save.label": "Save",
393
394
  "integration.editor.action.save.saving": "Saving…",
394
395
  "integration.editor.action.state.label": "Workflow status",
@@ -953,10 +954,9 @@
953
954
  "integration.revisions.error.loadFailed": "Could not load the revision history.",
954
955
  "integration.runs.action.back": "Back",
955
956
  "integration.runs.action.cancel": "Cancel run",
956
- "integration.runs.action.resume": "Resume",
957
+ "integration.runs.action.resume": "Resume from the failed step",
957
958
  "integration.runs.action.terminate": "Terminate",
958
959
  "integration.runs.confirm.cancel": "Cancel this run?",
959
- "integration.runs.confirm.resume": "Resume this run?",
960
960
  "integration.runs.confirm.terminate": "Terminate this run?",
961
961
  "integration.runs.detail.error.details.expired": "History is no longer available (retention expired and no snapshot).",
962
962
  "integration.runs.detail.error.details.generic": "Could not load Temporal details.",
@@ -996,6 +996,7 @@
996
996
  "integration.runs.detail.steps.loading": "Loading steps…",
997
997
  "integration.runs.detail.steps.payloadsLoading": "Loading this step's input and output…",
998
998
  "integration.runs.detail.steps.title": "Steps",
999
+ "integration.runs.detail.technical.title": "Technical details",
999
1000
  "integration.runs.detail.timeline.label.activity": "Activity",
1000
1001
  "integration.runs.detail.timeline.label.timer": "Timer",
1001
1002
  "integration.runs.detail.timeline.label.workflow": "Workflow",
@@ -1011,12 +1012,11 @@
1011
1012
  "integration.runs.menu.aria.actions": "Actions for run #{id}",
1012
1013
  "integration.runs.menu.cancel": "Cancel run",
1013
1014
  "integration.runs.menu.replay": "Replay",
1014
- "integration.runs.menu.resume": "Resume",
1015
- "integration.runs.menu.retry": "Retry",
1015
+ "integration.runs.menu.resume": "Resume from the failed step",
1016
+ "integration.runs.menu.retry": "Run again from the start",
1016
1017
  "integration.runs.menu.terminate": "Terminate",
1017
1018
  "integration.runs.menu.viewDetails": "View details",
1018
1019
  "integration.runs.notice.requested": "{action} requested.",
1019
- "integration.runs.notice.started": "Started run #{id}.",
1020
1020
  "integration.runs.payload.expertToggle.label": "Expert (JSON)",
1021
1021
  "integration.runs.payload.extrasNote": "Additional payload keys from the expert view are kept: {keys}",
1022
1022
  "integration.runs.payload.file.dropHint": "Drop a file here or click to choose",
@@ -1,6 +1,6 @@
1
1
  <script setup>
2
2
  import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
3
- import { AltArrowLeft as ArrowLeft, AltArrowRight as Chevron, CheckCircle, RefreshCircle as Loader2, MenuDots as MoreHorizontal, Play, Refresh as RetryIcon, Diskette as Save, SidebarMinimalistic as RailIcon } from "#solar-icons";
3
+ import { AltArrowDown as ArrowDown, AltArrowLeft as ArrowLeft, AltArrowRight as Chevron, CheckCircle, RefreshCircle as Loader2, MenuDots as MoreHorizontal, Play, Refresh as RetryIcon, Diskette as Save, SidebarMinimalistic as RailIcon } from "#solar-icons";
4
4
  import {
5
5
  AlertDialog,
6
6
  AlertDialogCancel,
@@ -48,7 +48,7 @@ import { payloadFieldsFromBlob } from "../../../data/payloadForm";
48
48
  import { eventTopicOf } from "../../../data/eventTrigger";
49
49
  import { isShapeIssue, saveRefusalLines } from "../../../data/saveRefusal";
50
50
  import WorkflowIconView from "../../../components/Integrations/WorkflowIconView.vue";
51
- import { DEFAULT_RUN_DIRECTION, DEFAULT_RUN_SORT } from "../../../utils/runFeedQuery";
51
+ import { DEFAULT_RUN_DIRECTION, DEFAULT_RUN_SORT, acceptsResume } from "../../../utils/runFeedQuery";
52
52
  import { keepOpenBeside } from "../../../utils/dialog";
53
53
  import { executionModeLabel } from "../../../utils/executionMode";
54
54
  const t = useIntegrationsT();
@@ -325,7 +325,7 @@ async function onRevisionRestored() {
325
325
  draft.value = null;
326
326
  settleBuildStatus();
327
327
  }
328
- const { trigger: triggerRun, loadForWorkflow, fetchRun, getRun, getSteps, retryRun } = useIntegrationsRuns();
328
+ const { trigger: triggerRun, loadForWorkflow, fetchRun, getRun, getSteps, retryRun, resumeRun } = useIntegrationsRuns();
329
329
  const running = ref(false);
330
330
  const waitingMode = ref(null);
331
331
  const payloadText = ref("{}");
@@ -766,13 +766,18 @@ function closeRun(target) {
766
766
  void router.replace({ query: { ...route.query, run: void 0, view: void 0 } });
767
767
  }
768
768
  const canRetryOpenRun = computed(() => openRun.value?.status === "failed");
769
+ const canResumeOpenRun = computed(() => {
770
+ const r = openRun.value;
771
+ return !!r && acceptsResume(r.apiStatus, r.status) && !!r.temporalRunId;
772
+ });
773
+ const canRerunOpenRun = computed(() => canRetryOpenRun.value || canResumeOpenRun.value);
769
774
  const retryBusy = ref(false);
770
- async function retryOpenRun() {
775
+ async function retryOpenRun(kind = "retry") {
771
776
  const source = openRun.value;
772
777
  if (!source || retryBusy.value) return;
773
778
  retryBusy.value = true;
774
779
  try {
775
- const res = await retryRun(source.workflowId, source.id);
780
+ const res = kind === "resume" ? await resumeRun(source.workflowId, source.id) : await retryRun(source.workflowId, source.id);
776
781
  const newId = res && typeof res === "object" && "id" in res ? String(res.id) : null;
777
782
  toast.success(newId && newId !== source.id ? t("integration.editor.retry.toast.started", { id: newId }) : t("integration.editor.retry.toast.requested"));
778
783
  void loadRuns(1, { silent: true });
@@ -780,7 +785,7 @@ async function retryOpenRun() {
780
785
  await router.push({ query: { ...route.query, run: newId, view: void 0 } });
781
786
  }
782
787
  } catch (e) {
783
- toast.error(errorReason(e, t("integration.editor.retry.error.generic")));
788
+ toast.error(errorReason(e, kind === "resume" ? t("integration.runs.error.resume") : t("integration.editor.retry.error.generic")));
784
789
  } finally {
785
790
  retryBusy.value = false;
786
791
  }
@@ -920,9 +925,10 @@ async function confirmDeactivate() {
920
925
  <Chevron :size="15" weight="Outline" class="shrink-0 text-muted-foreground/50" />
921
926
  <button type="button" class="shrink-0 font-normal text-muted-foreground/80 transition-colors hover:text-primary" @click="closeRun('runs')">{{ t("integration.editor.breadcrumb.runs") }}</button>
922
927
  <Chevron :size="15" weight="Outline" class="shrink-0 text-muted-foreground/50" />
923
- <!-- The Temporal run id IS the run's identity; the internal
924
- number only fills in until the run is handed off. -->
925
- <span class="shrink-0 font-mono">{{ openRun?.temporalRunId ?? t("integration.editor.breadcrumb.runNumber", { id: openRunId }) }}</span>
928
+ <!-- The run number the operator knows from the dialog and the
929
+ lists (PO-692). The engine's own run id is for whoever runs the
930
+ engine, and sits under the record's Technical details. -->
931
+ <span class="shrink-0">{{ t("integration.editor.breadcrumb.runNumber", { id: openRunId }) }}</span>
926
932
  </template>
927
933
  <template v-else>
928
934
  <span class="flex size-7 shrink-0 items-center justify-center rounded-md bg-slate-100 text-slate-500">
@@ -976,11 +982,21 @@ async function confirmDeactivate() {
976
982
 
977
983
  <!-- Run open: run-scoped actions replace the workflow Save. -->
978
984
  <template v-if="openRunId">
979
- <Button v-if="canRetryOpenRun" variant="outline" class="h-7 text-sm" :disabled="retryBusy" :title="t('integration.editor.action.retry.title')" @click="retryOpenRun">
980
- <Loader2 v-if="retryBusy" :size="12" class="animate-spin" />
981
- <RetryIcon v-else :size="12" />
982
- {{ t("integration.editor.action.retry") }}
983
- </Button>
985
+ <!-- The two ways back from a failed run, behind one button. -->
986
+ <DropdownMenu v-if="canRerunOpenRun">
987
+ <DropdownMenuTrigger as-child>
988
+ <Button variant="outline" class="h-7 text-sm" :disabled="retryBusy">
989
+ <Loader2 v-if="retryBusy" :size="12" class="animate-spin" />
990
+ <RetryIcon v-else :size="12" />
991
+ {{ t("integration.editor.action.rerun") }}
992
+ <ArrowDown :size="12" />
993
+ </Button>
994
+ </DropdownMenuTrigger>
995
+ <DropdownMenuContent align="end">
996
+ <DropdownMenuItem v-if="canResumeOpenRun" @select="retryOpenRun('resume')">{{ t("integration.runs.action.resume") }}</DropdownMenuItem>
997
+ <DropdownMenuItem v-if="canRetryOpenRun" :title="t('integration.editor.action.retry.title')" @select="retryOpenRun('retry')">{{ t("integration.editor.action.retry") }}</DropdownMenuItem>
998
+ </DropdownMenuContent>
999
+ </DropdownMenu>
984
1000
  <Button class="h-7 text-sm" @click="closeRun('editor')">
985
1001
  <ArrowLeft :size="12" />
986
1002
  {{ t("integration.editor.action.backToWorkflow") }}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenexx/studio-integrations",
3
- "version": "3.8.4",
3
+ "version": "3.8.5",
4
4
  "type": "module",
5
5
  "description": "Integration Studio (core: workflows, node editor, runs, schedules, credentials, secrets, audit) as a standalone, publishable Nuxt module.",
6
6
  "repository": {