Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/web/src/pages/routine-detail-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { runDetailPath } from "../insights-deeplinks";
import { Link } from "../navigation";
import { ROUTINES_PATH_PREFIX } from "../path-ids";
import { ROUTINE_HEALTH_TONE } from "../routine-health-tone";
import { useOpenRoutineInCanvas } from "../shell/canvas-availability";
import { StageTopBar } from "../shell/stage-top-bar";
import { nextRunLabel, RunStatusCell, TriggeredByCell } from "./routines-page";
import { listWorkflowDefinitions, useTenantQuery } from "../routines-api";
Expand Down Expand Up @@ -341,13 +342,15 @@ export function RoutineDetailPage({
onRunNow,
onToggleEnabled,
onSaveSchedule,
onEdit,
}: {
readonly row: GlobalRoutineRow;
readonly now: number;
readonly workflowName: string;
readonly onRunNow: () => Promise<void>;
readonly onToggleEnabled: (enabled: boolean) => void;
readonly onSaveSchedule: (expression: string) => Promise<void>;
readonly onEdit: () => void;
}) {
const health = routineHealth(row.routine, row.runs);
const latestRunId =
Expand All @@ -370,6 +373,9 @@ export function RoutineDetailPage({
>
{row.routine.enabled ? "Pause" : "Resume"}
</Button>
<Button type="button" variant="outline" size="sm" onClick={onEdit}>
Edit
</Button>
</>
}
/>
Expand Down Expand Up @@ -499,6 +505,7 @@ export function RoutineDetailRoute({
}) {
const routinesQuery = useGlobalRoutines();
const actions = useRoutineActions();
const openRoutine = useOpenRoutineInCanvas();
const rows = routinesQuery.kind === "ready" ? routinesQuery.data : [];
const resolution = resolveRoutineSegment(rows, segment);
const row = resolution.kind === "found" ? resolution.row : undefined;
Expand Down Expand Up @@ -582,6 +589,7 @@ export function RoutineDetailRoute({
onSaveSchedule={(expression) =>
actions.saveCronSchedule(resolved, expression)
}
onEdit={() => openRoutine({ routineId: resolved.routine.id })}
/>
);
}
35 changes: 24 additions & 11 deletions apps/web/src/pages/routines-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
TableRow,
} from "@corbits/react-ui";
import type { BadgeTone, RunStatus } from "@corbits/react-ui";
import { Clock, PlayCircle, Plus } from "@corbits/icons";
import { Clock, PencilSimple, PlayCircle, Plus } from "@corbits/icons";
import type { KeyboardEvent } from "react";
import {
routineHealth,
Expand Down Expand Up @@ -243,12 +243,14 @@ export function GlobalRoutinesList({
onToggleEnabled,
onRunNow,
onOpenWorkbench,
onEditRoutine,
}: {
readonly rows: readonly GlobalRoutineRow[];
readonly now: number;
readonly onToggleEnabled: (row: GlobalRoutineRow, enabled: boolean) => void;
readonly onRunNow: (row: GlobalRoutineRow) => Promise<void>;
readonly onOpenWorkbench: (workbenchId: string) => void;
readonly onEditRoutine: (row: GlobalRoutineRow) => void;
}) {
if (rows.length === 0) {
return (
Expand Down Expand Up @@ -341,22 +343,32 @@ export function GlobalRoutinesList({
/>
</TableCell>
<TableCell>
{health.state === "paused" || !row.routine.enabled ? (
<div className="flex items-center gap-2">
{health.state === "paused" || !row.routine.enabled ? (
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => onToggleEnabled(row, true)}
>
<PlayCircle /> Resume
</Button>
) : (
<RunNowButton
variant="outline"
size="sm"
onRun={() => onRunNow(row)}
/>
)}
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => onToggleEnabled(row, true)}
onClick={() => onEditRoutine(row)}
>
<PlayCircle /> Resume
<PencilSimple /> Edit
</Button>
) : (
<RunNowButton
variant="outline"
size="sm"
onRun={() => onRunNow(row)}
/>
)}
</div>
</TableCell>
</TableRow>
);
Expand Down Expand Up @@ -411,6 +423,7 @@ export function RoutinesRoute({
void actions.setEnabled(row, enabled);
}}
onRunNow={(row) => actions.runNow(row)}
onEditRoutine={(row) => openRoutine({ routineId: row.routine.id })}
onOpenWorkbench={(workbenchId) => {
const row = rows.find(
(r) => r.routine.deliveryWorkbenchId === workbenchId,
Expand Down
79 changes: 79 additions & 0 deletions apps/web/test/routine-detail-page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const pageProps = {
onRunNow: () => Promise.resolve(),
onToggleEnabled: (_enabled: boolean) => {},
onSaveSchedule: (_expression: string) => Promise.resolve(),
onEdit: () => {},
};

function renderPage(overrides: Partial<GlobalRoutineRow> = {}): string {
Expand Down Expand Up @@ -251,6 +252,22 @@ describe("RoutineDetailPage lifecycle actions", () => {
container.remove();
}
});

test("Edit opens the routine's editor", () => {
let edits = 0;
const { container, root } = mount({
onEdit: () => {
edits += 1;
},
});
try {
clickButton(container, "Edit");
expect(edits).toBe(1);
} finally {
act(() => root.unmount());
container.remove();
}
});
});

describe("RoutineScheduleSection", () => {
Expand Down Expand Up @@ -653,4 +670,66 @@ describe("RoutineDetailRoute", () => {
cleanup(container, root);
}
});

test("Edit opens this routine's canvas editor by id", async () => {
const { CanvasAvailabilityProvider } =
await import("../src/shell/canvas-availability");
const { RoutineDetailRoute } =
await import("../src/pages/routine-detail-page");
const { BenchProvider } = await import("../src/bench-context");
const { TestQueryProvider } = await import("./test-query-provider");

globalThis.fetch = mockFetch({
tnt_1: [routineRecord({ id: "rtn_mine", name: "My digest" })],
});
const opened: { routineId?: string | null }[] = [];
const container = document.createElement("div");
document.body.appendChild(container);
const root: Root = createRoot(container);
await act(async () => {
root.render(
<TestQueryProvider>
<NavigationProvider navigate={() => {}}>
<BenchProvider>
<CanvasAvailabilityProvider
allowed={false}
open={false}
profile={null}
artifact={null}
routine={null}
focus={false}
openProfile={() => {}}
openArtifact={() => {}}
openRoutine={(subject) => opened.push(subject)}
toggleFocus={() => {}}
close={() => {}}
>
{createElement(RoutineDetailRoute, {
segment: "rtn_mine",
navigate: () => {},
})}
</CanvasAvailabilityProvider>
</BenchProvider>
</NavigationProvider>
</TestQueryProvider>,
);
});
for (let i = 0; i < 8; i++) {
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 10));
});
}
try {
const editButton = [...container.querySelectorAll("button")].find(
(button) => button.textContent?.trim() === "Edit",
);
expect(editButton).not.toBeUndefined();
act(() => {
editButton?.click();
});
expect(opened).toEqual([{ routineId: "rtn_mine" }]);
} finally {
cleanup(container, root);
}
});
});
68 changes: 67 additions & 1 deletion apps/web/test/routines-page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const listProps = {
onToggleEnabled: (_row: GlobalRoutineRow, _enabled: boolean) => {},
onRunNow: (_row: GlobalRoutineRow) => Promise.resolve(),
onOpenWorkbench: (_workbenchId: string) => {},
onEditRoutine: (_row: GlobalRoutineRow) => {},
};

function renderList(rows: readonly GlobalRoutineRow[]): string {
Expand Down Expand Up @@ -347,6 +348,41 @@ describe("GlobalRoutinesList", () => {
}
});

test("Edit calls onEditRoutine with the row", () => {
const calls: GlobalRoutineRow[] = [];
const container = document.createElement("div");
document.body.appendChild(container);
const root: Root = createRoot(container);
act(() => {
root.render(
createElement(NavigationProvider, {
navigate: noop,
children: createElement(GlobalRoutinesList, {
rows: [row()],
...listProps,
onEditRoutine: (r: GlobalRoutineRow) => {
calls.push(r);
},
}),
}),
);
});
try {
const editButton = [...container.querySelectorAll("button")].find(
(button) => button.textContent?.trim() === "Edit",
);
expect(editButton).not.toBeUndefined();
act(() => {
editButton?.click();
});
expect(calls).toHaveLength(1);
expect(calls[0]?.routine.id).toBe("rtn_1");
} finally {
act(() => root.unmount());
container.remove();
}
});

test("clicking the delivery workbench opens it", () => {
const opened: string[] = [];
const container = document.createElement("div");
Expand Down Expand Up @@ -460,6 +496,7 @@ describe("RoutinesRoute — membership-based aggregation (CL-6362)", () => {

async function renderRoute(
navigate: (to: string) => void,
openRoutine: (subject: { routineId?: string | null }) => void = () => {},
): Promise<{ container: HTMLDivElement; root: Root }> {
const { BenchProvider } = await import("../src/bench-context");
const { CanvasAvailabilityProvider } =
Expand All @@ -484,7 +521,7 @@ describe("RoutinesRoute — membership-based aggregation (CL-6362)", () => {
focus={false}
openProfile={() => {}}
openArtifact={() => {}}
openRoutine={() => {}}
openRoutine={openRoutine}
toggleFocus={() => {}}
close={() => {}}
>
Expand Down Expand Up @@ -541,4 +578,33 @@ describe("RoutinesRoute — membership-based aggregation (CL-6362)", () => {
window.localStorage.clear();
}
});

test("Edit opens the routine's editor with its id, not a fresh draft", async () => {
const realFetch = globalThis.fetch;
globalThis.fetch = mockFetch();
const opened: { routineId?: string | null }[] = [];
const { container, root } = await renderRoute(
() => {},
(subject) => {
opened.push(subject);
},
);
try {
const editButtons = [...container.querySelectorAll("button")].filter(
(button) => button.textContent?.trim() === "Edit",
);
expect(editButtons.length).toBeGreaterThan(0);
act(() => {
editButtons[0]?.click();
});
expect(opened).toHaveLength(1);
expect(opened[0]?.routineId).not.toBeNull();
expect(opened[0]?.routineId).not.toBeUndefined();
} finally {
act(() => root.unmount());
container.remove();
globalThis.fetch = realFetch;
window.localStorage.clear();
}
});
});
Loading