Surface block failure_reason in UI for all block types (#SKY-7620) (#4672)

This commit is contained in:
Celal Zamanoglu
2026-02-11 02:23:32 +03:00
committed by GitHub
parent 76411b6022
commit 45cfbb2e75
3 changed files with 98 additions and 70 deletions

View File

@@ -12,6 +12,7 @@ import { useWorkflowRunTimelineQuery } from "../hooks/useWorkflowRunTimelineQuer
import { Status } from "@/api/types"; import { Status } from "@/api/types";
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea"; import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
import { isTaskVariantBlock } from "../types/workflowTypes"; import { isTaskVariantBlock } from "../types/workflowTypes";
import { statusIsAFailureType } from "@/routes/tasks/types";
function DebuggerRunOutput() { function DebuggerRunOutput() {
const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } = const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } =
@@ -49,6 +50,12 @@ function DebuggerRunOutput() {
isTaskVariantBlock(activeBlock) && isTaskVariantBlock(activeBlock) &&
activeBlock.status === Status.Completed; activeBlock.status === Status.Completed;
const showFailureReason =
activeBlock &&
activeBlock.status !== null &&
(statusIsAFailureType({ status: activeBlock.status }) ||
activeBlock.status === Status.Canceled);
const outputs = workflowRun?.outputs; const outputs = workflowRun?.outputs;
const fileUrls = workflowRun?.downloaded_file_urls ?? []; const fileUrls = workflowRun?.downloaded_file_urls ?? [];
const observerOutput = workflowRun?.task_v2?.output; const observerOutput = workflowRun?.task_v2?.output;
@@ -72,41 +79,36 @@ function DebuggerRunOutput() {
<div className="rounded bg-slate-elevation2 p-6"> <div className="rounded bg-slate-elevation2 p-6">
<div className="space-y-4"> <div className="space-y-4">
<h1 className="text-sm font-bold">Block Outputs</h1> <h1 className="text-sm font-bold">Block Outputs</h1>
{activeBlock.output === null ? ( {showFailureReason ? (
<div className="text-sm">This block has no outputs</div>
) : isTaskVariantBlock(activeBlock) ? (
<div className="space-y-2"> <div className="space-y-2">
<h2 className="text-sm"> <h2 className="text-sm">Failure Reason</h2>
{showExtractedInformation <AutoResizingTextarea
? "Extracted Information" value={
: "Failure Reason"} activeBlock.status === "canceled"
</h2> ? "This block was cancelled"
{showExtractedInformation ? ( : activeBlock.failure_reason ?? ""
<CodeEditor }
language="json" readOnly
value={JSON.stringify( />
(hasExtractedInformation(activeBlock.output) &&
activeBlock.output.extracted_information) ??
null,
null,
2,
)}
minHeight="96px"
maxHeight="200px"
readOnly
/>
) : (
<AutoResizingTextarea
value={
activeBlock.status === "canceled"
? "This block was cancelled"
: activeBlock.failure_reason ?? ""
}
readOnly
/>
)}
</div> </div>
) : ( ) : showExtractedInformation ? (
<div className="space-y-2">
<h2 className="text-sm">Extracted Information</h2>
<CodeEditor
language="json"
value={JSON.stringify(
(hasExtractedInformation(activeBlock.output) &&
activeBlock.output.extracted_information) ??
null,
null,
2,
)}
minHeight="96px"
maxHeight="200px"
readOnly
/>
</div>
) : activeBlock.output !== null ? (
<div className="space-y-2"> <div className="space-y-2">
<h2 className="text-sm">Output</h2> <h2 className="text-sm">Output</h2>
<CodeEditor <CodeEditor
@@ -117,6 +119,8 @@ function DebuggerRunOutput() {
readOnly readOnly
/> />
</div> </div>
) : (
<div className="text-sm">This block has no outputs</div>
)} )}
</div> </div>
</div> </div>

View File

@@ -12,6 +12,7 @@ import { useWorkflowRunTimelineQuery } from "../hooks/useWorkflowRunTimelineQuer
import { Status } from "@/api/types"; import { Status } from "@/api/types";
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea"; import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
import { isTaskVariantBlock } from "../types/workflowTypes"; import { isTaskVariantBlock } from "../types/workflowTypes";
import { statusIsAFailureType } from "@/routes/tasks/types";
function WorkflowRunOutput() { function WorkflowRunOutput() {
const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } = const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } =
@@ -49,6 +50,12 @@ function WorkflowRunOutput() {
isTaskVariantBlock(activeBlock) && isTaskVariantBlock(activeBlock) &&
activeBlock.status === Status.Completed; activeBlock.status === Status.Completed;
const showFailureReason =
activeBlock &&
activeBlock.status !== null &&
(statusIsAFailureType({ status: activeBlock.status }) ||
activeBlock.status === Status.Canceled);
const outputs = workflowRun?.outputs; const outputs = workflowRun?.outputs;
const fileUrls = workflowRun?.downloaded_file_urls ?? []; const fileUrls = workflowRun?.downloaded_file_urls ?? [];
const observerOutput = workflowRun?.task_v2?.output; const observerOutput = workflowRun?.task_v2?.output;
@@ -72,41 +79,36 @@ function WorkflowRunOutput() {
<div className="rounded bg-slate-elevation2 p-6"> <div className="rounded bg-slate-elevation2 p-6">
<div className="space-y-4"> <div className="space-y-4">
<h1 className="text-lg font-bold">Block Outputs</h1> <h1 className="text-lg font-bold">Block Outputs</h1>
{activeBlock.output === null ? ( {showFailureReason ? (
<div>This block has no outputs</div>
) : isTaskVariantBlock(activeBlock) ? (
<div className="space-y-2"> <div className="space-y-2">
<h2> <h2>Failure Reason</h2>
{showExtractedInformation <AutoResizingTextarea
? "Extracted Information" value={
: "Failure Reason"} activeBlock.status === "canceled"
</h2> ? "This block was cancelled"
{showExtractedInformation ? ( : activeBlock.failure_reason ?? ""
<CodeEditor }
language="json" readOnly
value={JSON.stringify( />
(hasExtractedInformation(activeBlock.output) &&
activeBlock.output.extracted_information) ??
null,
null,
2,
)}
minHeight="96px"
maxHeight="200px"
readOnly
/>
) : (
<AutoResizingTextarea
value={
activeBlock.status === "canceled"
? "This block was cancelled"
: activeBlock.failure_reason ?? ""
}
readOnly
/>
)}
</div> </div>
) : ( ) : showExtractedInformation ? (
<div className="space-y-2">
<h2>Extracted Information</h2>
<CodeEditor
language="json"
value={JSON.stringify(
(hasExtractedInformation(activeBlock.output) &&
activeBlock.output.extracted_information) ??
null,
null,
2,
)}
minHeight="96px"
maxHeight="200px"
readOnly
/>
</div>
) : activeBlock.output !== null ? (
<div className="space-y-2"> <div className="space-y-2">
<h2>Output</h2> <h2>Output</h2>
<CodeEditor <CodeEditor
@@ -117,6 +119,8 @@ function WorkflowRunOutput() {
readOnly readOnly
/> />
</div> </div>
) : (
<div>This block has no outputs</div>
)} )}
</div> </div>
</div> </div>

View File

@@ -57,7 +57,9 @@ function WorkflowRunTimelineItemInfoSection({ activeItem }: Props) {
if (isWorkflowRunBlock(item)) { if (isWorkflowRunBlock(item)) {
const showExtractedInformationTab = item.status === Status.Completed; const showExtractedInformationTab = item.status === Status.Completed;
const showFailureReasonTab = const showFailureReasonTab =
item.status && statusIsAFailureType({ status: item.status }); item.status &&
(statusIsAFailureType({ status: item.status }) ||
item.status === Status.Canceled);
const defaultTab = showExtractedInformationTab const defaultTab = showExtractedInformationTab
? "extracted_information" ? "extracted_information"
: showFailureReasonTab : showFailureReasonTab
@@ -81,7 +83,7 @@ function WorkflowRunTimelineItemInfoSection({ activeItem }: Props) {
Extracted Information Extracted Information
</TabsTrigger> </TabsTrigger>
)} )}
{item.status && statusIsAFailureType({ status: item.status }) && ( {showFailureReasonTab && (
<TabsTrigger value="failure_reason">Failure Reason</TabsTrigger> <TabsTrigger value="failure_reason">Failure Reason</TabsTrigger>
)} )}
<TabsTrigger value="navigation_goal">Navigation Goal</TabsTrigger> <TabsTrigger value="navigation_goal">Navigation Goal</TabsTrigger>
@@ -116,7 +118,7 @@ function WorkflowRunTimelineItemInfoSection({ activeItem }: Props) {
/> />
</TabsContent> </TabsContent>
)} )}
{item.status && statusIsAFailureType({ status: item.status }) && ( {showFailureReasonTab && (
<TabsContent value="failure_reason"> <TabsContent value="failure_reason">
<AutoResizingTextarea <AutoResizingTextarea
value={ value={
@@ -212,12 +214,30 @@ function WorkflowRunTimelineItemInfoSection({ activeItem }: Props) {
return null; return null;
} }
const fallbackDefaultTab = showFailureReasonTab
? "failure_reason"
: "output";
return ( return (
<div className="rounded bg-slate-elevation1 p-4"> <div className="rounded bg-slate-elevation1 p-4">
<Tabs key={item.block_type} defaultValue="output"> <Tabs key={item.block_type} defaultValue={fallbackDefaultTab}>
<TabsList> <TabsList>
{showFailureReasonTab && (
<TabsTrigger value="failure_reason">Failure Reason</TabsTrigger>
)}
<TabsTrigger value="output">Output</TabsTrigger> <TabsTrigger value="output">Output</TabsTrigger>
</TabsList> </TabsList>
{showFailureReasonTab && (
<TabsContent value="failure_reason">
<AutoResizingTextarea
value={
item.status === "canceled"
? "This block was cancelled"
: item.failure_reason ?? ""
}
readOnly
/>
</TabsContent>
)}
<TabsContent value="output"> <TabsContent value="output">
<CodeEditor <CodeEditor
value={JSON.stringify(item.output, null, 2)} value={JSON.stringify(item.output, null, 2)}