diff --git a/ui/src/components/IssueProperties.test.tsx b/ui/src/components/IssueProperties.test.tsx index f81c7fdaa54..927098b549c 100644 --- a/ui/src/components/IssueProperties.test.tsx +++ b/ui/src/components/IssueProperties.test.tsx @@ -1005,11 +1005,23 @@ describe("IssueProperties", () => { }), childIssues: [], onUpdate, + inline: true, }); await flush(); expect(container.textContent).toContain("Monitor"); expect(container.textContent).toContain("Next check"); + expect(container.querySelector('input[type="datetime-local"]')).toBeNull(); + expect(container.querySelector('input[placeholder="What should the agent re-check?"]')).toBeNull(); + + const monitorTrigger = Array.from(container.querySelectorAll("button")) + .find((button) => button.textContent?.includes("Next check")); + expect(monitorTrigger).not.toBeUndefined(); + + await act(async () => { + monitorTrigger!.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flush(); const inputs = Array.from(container.querySelectorAll("input")); const datetimeInput = inputs.find((input) => input.getAttribute("type") === "datetime-local"); diff --git a/ui/src/components/IssueProperties.tsx b/ui/src/components/IssueProperties.tsx index fac4ff7ef2c..26642f76c49 100644 --- a/ui/src/components/IssueProperties.tsx +++ b/ui/src/components/IssueProperties.tsx @@ -33,7 +33,7 @@ import { formatDate, cn, projectUrl } from "../lib/utils"; import { timeAgo } from "../lib/timeAgo"; import { Separator } from "@/components/ui/separator"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import { User, Hexagon, ArrowUpRight, Tag, Plus, GitBranch, FolderOpen, Check, ExternalLink } from "lucide-react"; +import { User, Hexagon, ArrowUpRight, Tag, Plus, GitBranch, FolderOpen, Check, ExternalLink, Clock } from "lucide-react"; import { AgentIcon } from "./AgentIconPicker"; function TruncatedCopyable({ value, icon: Icon }: { value: string; icon: React.ComponentType<{ className?: string }> }) { @@ -126,6 +126,19 @@ 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[]; @@ -227,6 +240,7 @@ export function IssueProperties({ const [reviewerSearch, setReviewerSearch] = useState(""); const [approversOpen, setApproversOpen] = useState(false); const [approverSearch, setApproverSearch] = useState(""); + const [monitorOpen, setMonitorOpen] = useState(false); const [labelsOpen, setLabelsOpen] = useState(false); const [labelSearch, setLabelSearch] = useState(""); const [newLabelName, setNewLabelName] = useState(""); @@ -520,8 +534,12 @@ export function IssueProperties({ serviceName, externalRef, }); + setMonitorOpen(false); + }; + const clearMonitor = () => { + updateMonitor(null); + setMonitorOpen(false); }; - const clearMonitor = () => updateMonitor(null); const currentMonitorLabel = (() => { if (issue.executionPolicy?.monitor?.nextCheckAt) { return `Next check ${formatDate(new Date(issue.executionPolicy.monitor.nextCheckAt))}`; @@ -534,6 +552,87 @@ export function IssueProperties({ } return "Not scheduled"; })(); + const monitorNextCheckAt = issue.executionPolicy?.monitor?.nextCheckAt ?? null; + const monitorTrigger = ( + + {monitorNextCheckAt ? ( + + ); + const monitorAttemptBadge = issue.monitorAttemptCount && issue.monitorAttemptCount > 0 ? ( + + Attempt {issue.monitorAttemptCount} + + ) : null; + const monitorContent = ( +
+
+ setMonitorAtInput(e.target.value)} + /> + setMonitorNotesInput(e.target.value)} + /> +
+
+ setMonitorServiceInput(e.target.value)} + /> + setMonitorExternalRefInput(e.target.value)} + /> +
+ + {issue.executionPolicy?.monitor ? ( + + ) : null} +
+
+
+ ); const selectedIssueLabels = useMemo(() => { const selectedIds = issue.labelIds ?? []; @@ -1324,68 +1423,18 @@ export function IssueProperties({ )} - -
-
- {currentMonitorLabel} - {issue.monitorAttemptCount && issue.monitorAttemptCount > 0 ? ( - - Attempt {issue.monitorAttemptCount} - - ) : null} -
-
- setMonitorAtInput(e.target.value)} - /> - setMonitorNotesInput(e.target.value)} - /> -
-
- setMonitorServiceInput(e.target.value)} - /> - setMonitorExternalRefInput(e.target.value)} - /> -
- - {issue.executionPolicy?.monitor ? ( - - ) : null} -
-
-
-
+ + {monitorContent} + {issue.requestDepth > 0 && (