From 34c578baa6681f119e13fdd962d42f2038f9a52a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 09:06:50 +0000 Subject: [PATCH 1/2] Initial plan From a1aa6a4f7998e487d60795e3fdb76e1ea00d10f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 09:24:46 +0000 Subject: [PATCH 2/2] Improved delay actions reporting: sleeping icon, countdown display, timestamp hover card Co-authored-by: moshloop <1489660+moshloop@users.noreply.github.com> --- package-lock.json | 2 +- src/api/types/playbooks.ts | 1 + .../Runs/Actions/PlaybookRunsActionItem.tsx | 73 ++++++++++++++++--- .../PlaybookRunsActionItem.unit.test.tsx | 46 ++++++++++++ src/ui/Icons/PlaybookStatusIcon.tsx | 3 +- 5 files changed, 112 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 273fa2a89..cee093c27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "@flanksource/flanksource-ui", - "version": "1.4.215", + "version": "1.4.217", "dependencies": { "@ai-sdk/anthropic": "^3.0.1", "@ai-sdk/mcp": "^1.0.1", diff --git a/src/api/types/playbooks.ts b/src/api/types/playbooks.ts index 64f8ff97f..a04fafa9c 100644 --- a/src/api/types/playbooks.ts +++ b/src/api/types/playbooks.ts @@ -123,6 +123,7 @@ export type PlaybookResourceSelector = { export interface PlaybookAction { name: string; + delay?: string; // Action type specific fields ai?: any; diff --git a/src/components/Playbooks/Runs/Actions/PlaybookRunsActionItem.tsx b/src/components/Playbooks/Runs/Actions/PlaybookRunsActionItem.tsx index 9f3403ce7..039fa1e63 100644 --- a/src/components/Playbooks/Runs/Actions/PlaybookRunsActionItem.tsx +++ b/src/components/Playbooks/Runs/Actions/PlaybookRunsActionItem.tsx @@ -8,6 +8,61 @@ import { PlaybookStatusIcon } from "../../../../ui/Icons/PlaybookStatusIcon"; import { Badge } from "@flanksource-ui/ui/Badge/Badge"; import { TbCornerDownRight } from "react-icons/tb"; +function ActionTimestampTooltip({ + action +}: { + action: Pick< + PlaybookRunAction, + "id" | "status" | "scheduled_time" | "start_time" | "end_time" + >; +}) { + const fmt = (t?: string) => + t ? dayjs(t).local().format("YYYY-MM-DD HH:mm:ss") : null; + + const scheduledFmt = fmt(action.scheduled_time); + const startFmt = fmt(action.start_time); + const endFmt = fmt(action.end_time); + + const delayMs = + action.scheduled_time && action.start_time + ? dayjs(action.start_time).diff(dayjs(action.scheduled_time)) + : 0; + + const showDelay = delayMs > 1000; + + if (!scheduledFmt && !startFmt && !endFmt) { + return null; + } + + return ( +
+ {scheduledFmt && ( +
+ Scheduled: + {scheduledFmt} +
+ )} + {startFmt && ( +
+ Started: + {startFmt} + {showDelay && action.scheduled_time && ( + + (Δ {relativeDateTime(action.scheduled_time, action.start_time)}) + + )} +
+ )} + {endFmt && ( +
+ Ended: + {endFmt} +
+ )} +
+ ); +} + type PlaybookRunsActionItemProps = { agent?: string; action: Pick< @@ -44,6 +99,7 @@ export default function PlaybookRunsActionItem({
{ if (action.status !== "skipped") { onClick(); @@ -55,7 +111,6 @@ export default function PlaybookRunsActionItem({ isSelected ? "bg-gray-200" : "bg-white", action.status === "skipped" ? "cursor-not-allowed" : "cursor-pointer" )} - data-tooltip-content={action.status} >
@@ -74,14 +129,9 @@ export default function PlaybookRunsActionItem({
{action.status === "sleeping" ? ( - - - + + in{" "} + {relativeDateTime(dayjs().toISOString(), action.scheduled_time)} ) : ( )}
-
- + + +
); } diff --git a/src/components/Playbooks/Runs/Actions/__tests__/PlaybookRunsActionItem.unit.test.tsx b/src/components/Playbooks/Runs/Actions/__tests__/PlaybookRunsActionItem.unit.test.tsx index fb5a890b9..fd6a55483 100644 --- a/src/components/Playbooks/Runs/Actions/__tests__/PlaybookRunsActionItem.unit.test.tsx +++ b/src/components/Playbooks/Runs/Actions/__tests__/PlaybookRunsActionItem.unit.test.tsx @@ -55,4 +55,50 @@ describe("PlaybookRunsActionItem", () => { expect(screen.getByRole("button")).toHaveClass("bg-gray-200"); }); + + it("shows 'in X' for sleeping actions with scheduled_time", () => { + const futureTime = new Date(Date.now() + 5 * 60 * 1000).toISOString(); + const sleepingAction: PlaybookRunAction = { + playbook_run_id: "1", + id: "2", + name: "Sleeping Action", + status: "sleeping", + start_time: "2022-01-01T00:00:00Z", + scheduled_time: futureTime + }; + + render( + + ); + + // The sleeping action should show "in X" instead of "-" + const sleepText = screen.getByText(/^in /); + expect(sleepText).toBeInTheDocument(); + }); + + it("does not call onClick for skipped actions", () => { + const skippedAction: PlaybookRunAction = { + ...mockAction, + id: "3", + status: "skipped" + }; + const onClickForSkipped = jest.fn(); + + render( + + ); + + fireEvent.click(screen.getByRole("button")); + expect(onClickForSkipped).not.toHaveBeenCalled(); + }); }); diff --git a/src/ui/Icons/PlaybookStatusIcon.tsx b/src/ui/Icons/PlaybookStatusIcon.tsx index ca4d67a03..cab9f16d3 100644 --- a/src/ui/Icons/PlaybookStatusIcon.tsx +++ b/src/ui/Icons/PlaybookStatusIcon.tsx @@ -4,6 +4,7 @@ import { BsCircle, BsClock, BsHourglassSplit, + BsMoon, BsSlashCircle, BsStopCircle, BsXCircle @@ -67,7 +68,7 @@ export function PlaybookStatusIcon({ case "sleeping": return ( - + ); case "skipped":