diff --git a/docs/pr-screenshots/pap-2945/monitor-surfaces.png b/docs/pr-screenshots/pap-2945/monitor-surfaces.png index 1d96eb5f2d0..a84ad637d2d 100644 Binary files a/docs/pr-screenshots/pap-2945/monitor-surfaces.png and b/docs/pr-screenshots/pap-2945/monitor-surfaces.png differ diff --git a/ui/src/components/IssueMonitorActivityCard.tsx b/ui/src/components/IssueMonitorActivityCard.tsx index 2cf845ea672..bc1716fc0a9 100644 --- a/ui/src/components/IssueMonitorActivityCard.tsx +++ b/ui/src/components/IssueMonitorActivityCard.tsx @@ -1,20 +1,8 @@ import type { Issue } from "@paperclipai/shared"; import { Button } from "@/components/ui/button"; +import { formatMonitorOffset } from "@/lib/issue-monitor"; import { formatDateTime } from "@/lib/utils"; -function formatMonitorOffset(nextCheckAt: Date | string): string { - const deltaMs = new Date(nextCheckAt).getTime() - Date.now(); - const absMinutes = Math.round(Math.abs(deltaMs) / 60_000); - if (absMinutes <= 0) return "now"; - if (absMinutes < 60) return deltaMs >= 0 ? `in ${absMinutes}m` : `${absMinutes}m ago`; - - const absHours = Math.round(absMinutes / 60); - if (absHours < 24) return deltaMs >= 0 ? `in ${absHours}h` : `${absHours}h ago`; - - const absDays = Math.round(absHours / 24); - return deltaMs >= 0 ? `in ${absDays}d` : `${absDays}d ago`; -} - function resolveScheduledMonitor(issue: Issue) { const nextCheckAt = issue.monitorNextCheckAt?.toISOString() ?? diff --git a/ui/src/components/IssueProperties.tsx b/ui/src/components/IssueProperties.tsx index 7989c942f94..da36be76d70 100644 --- a/ui/src/components/IssueProperties.tsx +++ b/ui/src/components/IssueProperties.tsx @@ -25,6 +25,7 @@ import { getRecentProjectIds, trackRecentProject } from "../lib/recent-projects" import { orderItemsBySelectedAndRecent } from "../lib/recent-selections"; import { formatAssigneeUserLabel } from "../lib/assignees"; import { buildExecutionPolicy, stageParticipantValues } from "../lib/issue-execution-policy"; +import { formatMonitorOffset } from "../lib/issue-monitor"; import { StatusIcon } from "./StatusIcon"; import { PriorityIcon } from "./PriorityIcon"; import { Identity } from "./Identity"; @@ -126,19 +127,6 @@ function toDateTimeLocalValue(value: string | null | undefined) { return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16); } -function formatMonitorOffset(nextCheckAt: Date | string): string { - const deltaMs = new Date(nextCheckAt).getTime() - Date.now(); - const absMinutes = Math.round(Math.abs(deltaMs) / 60_000); - if (absMinutes <= 0) return "now"; - if (absMinutes < 60) return deltaMs >= 0 ? `in ${absMinutes}m` : `${absMinutes}m ago`; - - const absHours = Math.round(absMinutes / 60); - if (absHours < 24) return deltaMs >= 0 ? `in ${absHours}h` : `${absHours}h ago`; - - const absDays = Math.round(absHours / 24); - return deltaMs >= 0 ? `in ${absDays}d` : `${absDays}d ago`; -} - interface IssuePropertiesProps { issue: Issue; childIssues?: Issue[]; @@ -540,12 +528,12 @@ export function IssueProperties({ if (issue.executionPolicy?.monitor?.nextCheckAt) { return `Next check ${formatDate(new Date(issue.executionPolicy.monitor.nextCheckAt))}`; } - if (issue.monitorLastTriggeredAt) { - return `Last triggered ${timeAgo(issue.monitorLastTriggeredAt)}`; - } if (issue.executionState?.monitor?.status === "cleared") { return "Cleared"; } + if (issue.monitorLastTriggeredAt) { + return `Last triggered ${timeAgo(issue.monitorLastTriggeredAt)}`; + } return "Not scheduled"; })(); const monitorNextCheckAt = issue.executionPolicy?.monitor?.nextCheckAt ?? null; diff --git a/ui/src/lib/issue-monitor.ts b/ui/src/lib/issue-monitor.ts new file mode 100644 index 00000000000..3b0f8bde401 --- /dev/null +++ b/ui/src/lib/issue-monitor.ts @@ -0,0 +1,12 @@ +export function formatMonitorOffset(nextCheckAt: Date | string): string { + const deltaMs = new Date(nextCheckAt).getTime() - Date.now(); + const absMinutes = Math.round(Math.abs(deltaMs) / 60_000); + if (absMinutes <= 0) return "now"; + if (absMinutes < 60) return deltaMs >= 0 ? `in ${absMinutes}m` : `${absMinutes}m ago`; + + const absHours = Math.round(absMinutes / 60); + if (absHours < 24) return deltaMs >= 0 ? `in ${absHours}h` : `${absHours}h ago`; + + const absDays = Math.round(absHours / 24); + return deltaMs >= 0 ? `in ${absDays}d` : `${absDays}d ago`; +} diff --git a/ui/storybook/stories/monitor-surfaces.stories.tsx b/ui/storybook/stories/monitor-surfaces.stories.tsx index c6473652824..a8af38f5e8e 100644 --- a/ui/storybook/stories/monitor-surfaces.stories.tsx +++ b/ui/storybook/stories/monitor-surfaces.stories.tsx @@ -91,6 +91,36 @@ const clearedIssue: Issue = { executionPolicy: { ...(baseIssue.executionPolicy ?? { mode: "normal", commentRequired: true, stages: [] }), }, + executionState: { + ...(baseIssue.executionState ?? { + status: "pending", + currentStageId: null, + currentStageIndex: null, + currentStageType: null, + currentParticipant: null, + returnAssignee: null, + reviewRequest: null, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }), + monitor: { + status: "cleared", + nextCheckAt: null, + lastTriggeredAt: null, + attemptCount: 0, + notes: null, + scheduledBy: "board", + kind: null, + serviceName: null, + externalRef: null, + timeoutAt: null, + maxAttempts: null, + recoveryPolicy: null, + clearedAt: new Date(Date.now() - 60_000).toISOString(), + clearReason: "manual", + }, + }, }; function MonitorSurfaceStories() {