Skip to content

Commit 6d27479

Browse files
authored
[local-explorer-ui] Fix Workflows page (colors, icons, etc) (#13654)
1 parent ed2f4ec commit 6d27479

10 files changed

Lines changed: 305 additions & 147 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@cloudflare/local-explorer-ui": patch
3+
---
4+
5+
fix: Preserve expanded workflow steps across polling and improve instance page UX
6+
7+
Expanded steps no longer collapse when new steps arrive via polling. Expansion state is lifted into the parent component and tracked by stable step keys, and polling skips state updates when data hasn't changed.
8+
9+
Also: navigate to instance page after triggering, distinct refresh/restart icons, toolbar layout below stats strip, always-visible stats, unified status colors, and visual-only stripping of internal step name suffixes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"miniflare": patch
3+
---
4+
5+
fix: Preserve internal counter suffix on workflow step names in local explorer API
6+
7+
Stop stripping the `-N` suffix from step names in the API response so the UI can distinguish duplicate step names. The suffix is now stripped only visually in the UI.

packages/local-explorer-ui/src/__e2e__/workflows/workflow.spec.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ describe("Workflows", () => {
8383
await waitForText("Trigger this workflow?");
8484
});
8585

86-
test("triggers a new instance with params", async () => {
86+
test("triggers a new instance and navigates to its detail page", async () => {
8787
await navigateToWorkflow(WORKFLOW_NAME);
8888

8989
await clickButton("Trigger");
@@ -95,10 +95,9 @@ describe("Workflows", () => {
9595

9696
await dialog.getByRole("button", { name: "Trigger Instance" }).click();
9797

98-
await page.waitForSelector('[role="dialog"]', {
99-
state: "hidden",
100-
timeout: 10_000,
101-
});
98+
// Should navigate to the instance detail page
99+
await waitForText("Steps Completed", { timeout: 10_000 });
100+
await waitForText("Input params", { timeout: 10_000 });
102101
});
103102

104103
test("cancels the trigger dialog", async () => {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { describe, test } from "vitest";
2+
import {
3+
getStepDisplayName,
4+
getStepKey,
5+
} from "../../components/workflows/StepRow";
6+
7+
describe("getStepKey", () => {
8+
test("produces key from type and name", ({ expect }) => {
9+
expect(getStepKey({ type: "step", name: "fetch-1" })).toBe("step-fetch-1");
10+
});
11+
12+
test("different counter suffixes produce different keys", ({ expect }) => {
13+
const a = getStepKey({ type: "step", name: "fetch-1" });
14+
const b = getStepKey({ type: "step", name: "fetch-2" });
15+
expect(a).not.toBe(b);
16+
});
17+
18+
test("same name with different types produce different keys", ({
19+
expect,
20+
}) => {
21+
const a = getStepKey({ type: "step", name: "process-1" });
22+
const b = getStepKey({ type: "waitForEvent", name: "process-1" });
23+
expect(a).not.toBe(b);
24+
});
25+
26+
test("handles undefined type and name", ({ expect }) => {
27+
expect(getStepKey({})).toBe("undefined-undefined");
28+
});
29+
});
30+
31+
describe("getStepDisplayName", () => {
32+
test("strips trailing counter suffix", ({ expect }) => {
33+
expect(getStepDisplayName("fetch-1")).toBe("fetch");
34+
});
35+
36+
test("strips multi-digit counter suffix", ({ expect }) => {
37+
expect(getStepDisplayName("fetch-12")).toBe("fetch");
38+
});
39+
40+
test("preserves name when no suffix", ({ expect }) => {
41+
expect(getStepDisplayName("fetch")).toBe("fetch");
42+
});
43+
44+
test("only strips the last numeric suffix", ({ expect }) => {
45+
expect(getStepDisplayName("retry-3-1")).toBe("retry-3");
46+
});
47+
48+
test("returns 'Unknown step' for undefined", ({ expect }) => {
49+
expect(getStepDisplayName(undefined)).toBe("Unknown step");
50+
});
51+
52+
test("handles name that is just a number suffix", ({ expect }) => {
53+
expect(getStepDisplayName("step-0")).toBe("step");
54+
});
55+
});

packages/local-explorer-ui/src/components/workflows/CreateInstanceDialog.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useCallback, useState } from "react";
33
import { workflowsCreateInstance } from "../../api";
44

55
interface CreateWorkflowInstanceDialogProps {
6-
onCreated: () => void;
6+
onCreated: (instanceId: string) => void;
77
onOpenChange: (open: boolean) => void;
88
open: boolean;
99
workflowName: string;
@@ -68,13 +68,20 @@ export function CreateWorkflowInstanceDialog({
6868
body.params = result.value;
6969
}
7070

71-
await workflowsCreateInstance({
71+
const response = await workflowsCreateInstance({
7272
path: { workflow_name: workflowName },
7373
body,
7474
});
7575

76+
const newId = (response.data?.result as { id?: string } | undefined)?.id;
77+
78+
if (!newId) {
79+
setError("Instance was created but no ID was returned");
80+
return;
81+
}
82+
7683
resetForm();
77-
onCreated();
84+
onCreated(newId);
7885
} catch (err) {
7986
setError(
8087
err instanceof Error ? err.message : "Failed to create instance"

packages/local-explorer-ui/src/components/workflows/StatusBadge.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import type { WorkflowsInstance } from "../../api";
44
type WorkflowStatus = NonNullable<WorkflowsInstance["status"]>;
55

66
const statusStyles: Record<WorkflowStatus, string> = {
7-
complete: "bg-kumo-success text-white",
8-
errored: "bg-kumo-danger text-white",
9-
terminated: "bg-kumo-danger text-white",
7+
complete: "bg-kumo-badge-teal text-white",
8+
errored: "bg-kumo-badge-red text-white",
9+
terminated: "bg-kumo-badge-red text-white",
1010
waiting: "bg-kumo-brand text-white",
1111
paused: "bg-kumo-brand text-white",
1212
running: "bg-kumo-brand text-white",

packages/local-explorer-ui/src/components/workflows/StepRow.tsx

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Loader } from "@cloudflare/kumo";
22
import { CheckIcon, PlusIcon } from "@phosphor-icons/react";
3-
import { useState } from "react";
3+
import { memo } from "react";
44
import { CopyButton } from "./CopyButton";
55
import { formatDuration, formatJson } from "./helpers";
66
import { ScrollableCodeBlock } from "./ScrollableCodeBlock";
@@ -27,7 +27,7 @@ function StepStatusIcon({
2727
);
2828
}
2929
return (
30-
<div className="flex size-5 items-center justify-center rounded bg-kumo-danger">
30+
<div className="flex size-5 items-center justify-center rounded bg-kumo-badge-red">
3131
<span className="text-xs font-bold text-white">!</span>
3232
</div>
3333
);
@@ -41,7 +41,7 @@ function StepStatusIcon({
4141
);
4242
}
4343
return (
44-
<div className="flex size-5 items-center justify-center rounded bg-kumo-success">
44+
<div className="flex size-5 items-center justify-center rounded bg-kumo-badge-teal">
4545
<CheckIcon size={12} weight="bold" className="text-white" />
4646
</div>
4747
);
@@ -56,23 +56,38 @@ function StepStatusIcon({
5656
const TYPE_BADGE_STYLES: Record<string, string> = {
5757
step: "bg-kumo-tint text-kumo-subtle",
5858
sleep: "bg-kumo-overlay text-kumo-subtle",
59-
waitForEvent: "bg-kumo-brand/10 text-kumo-brand",
59+
waitForEvent: "bg-kumo-badge-purple/10 text-kumo-badge-purple",
6060
};
6161

62-
export function StepRow({ step }: { step: StepData }): JSX.Element {
63-
const [expanded, setExpanded] = useState(false);
62+
export function getStepKey(step: StepData): string {
63+
return `${step.type}-${step.name}`;
64+
}
65+
66+
/** Strip the internal "-N" counter suffix for display purposes */
67+
export function getStepDisplayName(name: string | undefined): string {
68+
return (name ?? "Unknown step").replace(/-\d+$/, "");
69+
}
6470

71+
export const StepRow = memo(function StepRow({
72+
step,
73+
isExpanded,
74+
onToggleExpanded,
75+
}: {
76+
step: StepData;
77+
isExpanded: boolean;
78+
onToggleExpanded: () => void;
79+
}): JSX.Element {
6580
const hasDetails =
6681
step.type === "step" ||
6782
(step.type === "waitForEvent" &&
68-
(step.error || step.output !== undefined || step.finished));
83+
(step.error || step.output != null || step.finished));
6984

7085
return (
7186
<div className="border-b border-kumo-fill p-1 last:border-b-0">
7287
{/* Collapsed row */}
7388
<div
7489
className={`grid h-10 grid-cols-[20px_1fr_160px_160px_80px_24px] items-center gap-3 rounded-lg px-2 transition-colors ${hasDetails ? "cursor-pointer hover:bg-kumo-fill" : ""}`}
75-
onClick={hasDetails ? () => setExpanded(!expanded) : undefined}
90+
onClick={hasDetails ? onToggleExpanded : undefined}
7691
>
7792
<StepStatusIcon
7893
success={step.success}
@@ -89,7 +104,7 @@ export function StepRow({ step }: { step: StepData }): JSX.Element {
89104
</span>
90105
)}
91106
<span className="truncate text-sm text-kumo-default">
92-
{step.name ?? "Unknown step"}
107+
{getStepDisplayName(step.name)}
93108
</span>
94109
</div>
95110

@@ -103,7 +118,7 @@ export function StepRow({ step }: { step: StepData }): JSX.Element {
103118
{hasDetails ? (
104119
<PlusIcon
105120
size={14}
106-
className={`text-kumo-subtle transition-transform ${expanded ? "rotate-45" : ""}`}
121+
className={`text-kumo-subtle transition-transform ${isExpanded ? "rotate-45" : ""}`}
107122
/>
108123
) : (
109124
<div className="w-6" />
@@ -112,7 +127,7 @@ export function StepRow({ step }: { step: StepData }): JSX.Element {
112127
</div>
113128

114129
{/* Expanded detail panel */}
115-
{expanded && hasDetails && (
130+
{isExpanded && hasDetails && (
116131
<div className="-mx-1 -mb-1">
117132
<div className="mt-1 h-2 rounded-t-lg border-t border-kumo-fill" />
118133
<div className="px-4 pt-3 pb-4">
@@ -125,7 +140,7 @@ export function StepRow({ step }: { step: StepData }): JSX.Element {
125140
)}
126141
</div>
127142
);
128-
}
143+
});
129144

130145
function StepCodeCard({
131146
label,

0 commit comments

Comments
 (0)