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":