Fix live run indicator: only show blue dot when a run is actually active
The blue dot and LiveRunWidget were driven by `routine.activeIssue`, which returns any open execution issue — even after the heartbeat run finishes. Now checks routineRuns for status "received" or "issue_created" to determine if a run is actually in progress. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
c5f20a9891
commit
4fc80bdc16
1 changed files with 62 additions and 27 deletions
|
|
@ -30,7 +30,7 @@ import { AgentIcon } from "../components/AgentIconPicker";
|
||||||
import { InlineEntitySelector, type InlineEntityOption } from "../components/InlineEntitySelector";
|
import { InlineEntitySelector, type InlineEntityOption } from "../components/InlineEntitySelector";
|
||||||
import { MarkdownEditor, type MarkdownEditorRef } from "../components/MarkdownEditor";
|
import { MarkdownEditor, type MarkdownEditorRef } from "../components/MarkdownEditor";
|
||||||
import { ScheduleEditor, describeSchedule } from "../components/ScheduleEditor";
|
import { ScheduleEditor, describeSchedule } from "../components/ScheduleEditor";
|
||||||
import { RunButton, PauseResumeButton } from "../components/AgentActionButtons";
|
import { RunButton } from "../components/AgentActionButtons";
|
||||||
import { getRecentAssigneeIds, sortAgentsByRecency, trackRecentAssignee } from "../lib/recent-assignees";
|
import { getRecentAssigneeIds, sortAgentsByRecency, trackRecentAssignee } from "../lib/recent-assignees";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||||
|
|
@ -280,15 +280,19 @@ export function RoutineDetail() {
|
||||||
queryKey: queryKeys.routines.detail(routineId!),
|
queryKey: queryKeys.routines.detail(routineId!),
|
||||||
queryFn: () => routinesApi.get(routineId!),
|
queryFn: () => routinesApi.get(routineId!),
|
||||||
enabled: !!routineId,
|
enabled: !!routineId,
|
||||||
refetchInterval: (query) => query.state.data?.activeIssue ? 5000 : false,
|
|
||||||
});
|
});
|
||||||
const hasLiveRun = !!routine?.activeIssue;
|
|
||||||
const { data: routineRuns } = useQuery({
|
const { data: routineRuns } = useQuery({
|
||||||
queryKey: queryKeys.routines.runs(routineId!),
|
queryKey: queryKeys.routines.runs(routineId!),
|
||||||
queryFn: () => routinesApi.listRuns(routineId!),
|
queryFn: () => routinesApi.listRuns(routineId!),
|
||||||
enabled: !!routineId,
|
enabled: !!routineId,
|
||||||
refetchInterval: hasLiveRun ? 3000 : false,
|
refetchInterval: (query) => {
|
||||||
|
const runs = query.state.data ?? [];
|
||||||
|
return runs.some((r) => r.status === "received" || r.status === "issue_created") ? 3000 : false;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
const hasLiveRun = (routineRuns ?? []).some(
|
||||||
|
(run) => run.status === "received" || run.status === "issue_created",
|
||||||
|
);
|
||||||
const relatedActivityIds = useMemo(
|
const relatedActivityIds = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
triggerIds: routine?.triggers.map((trigger) => trigger.id) ?? [],
|
triggerIds: routine?.triggers.map((trigger) => trigger.id) ?? [],
|
||||||
|
|
@ -442,7 +446,7 @@ export function RoutineDetail() {
|
||||||
mutationFn: (status: string) => routinesApi.update(routineId!, { status }),
|
mutationFn: (status: string) => routinesApi.update(routineId!, { status }),
|
||||||
onSuccess: async (_data, status) => {
|
onSuccess: async (_data, status) => {
|
||||||
pushToast({
|
pushToast({
|
||||||
title: status === "paused" ? "Routine paused" : "Routine resumed",
|
title: status === "paused" ? "Automation paused" : "Automation enabled",
|
||||||
tone: "success",
|
tone: "success",
|
||||||
});
|
});
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
|
@ -607,29 +611,57 @@ export function RoutineDetail() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const automationEnabled = routine.status === "active";
|
||||||
|
const automationToggleDisabled = updateRoutineStatus.isPending || routine.status === "archived";
|
||||||
|
const automationLabel = routine.status === "archived" ? "Archived" : automationEnabled ? "Active" : "Paused";
|
||||||
|
const automationLabelClassName = routine.status === "archived"
|
||||||
|
? "text-muted-foreground"
|
||||||
|
: automationEnabled
|
||||||
|
? "text-emerald-400"
|
||||||
|
: "text-muted-foreground";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-2xl space-y-6">
|
<div className="max-w-2xl space-y-6">
|
||||||
{/* Header: status + actions */}
|
{/* Header: status + actions */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||||
<Badge variant={routine.status === "active" ? "default" : "secondary"}>
|
<div className="space-y-1">
|
||||||
{routine.status.replaceAll("_", " ")}
|
{routine.activeIssue && (
|
||||||
</Badge>
|
<Link
|
||||||
{routine.activeIssue && (
|
to={`/issues/${routine.activeIssue.identifier ?? routine.activeIssue.id}`}
|
||||||
<Link
|
className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:underline"
|
||||||
to={`/issues/${routine.activeIssue.identifier ?? routine.activeIssue.id}`}
|
>
|
||||||
className="text-xs text-muted-foreground hover:underline"
|
<span>Current issue</span>
|
||||||
>
|
<span>{routine.activeIssue.identifier ?? routine.activeIssue.id.slice(0, 8)}</span>
|
||||||
{routine.activeIssue.identifier ?? routine.activeIssue.id.slice(0, 8)}
|
</Link>
|
||||||
</Link>
|
)}
|
||||||
)}
|
</div>
|
||||||
<div className="ml-auto flex items-center gap-2">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<RunButton onClick={() => runRoutine.mutate()} disabled={runRoutine.isPending} />
|
<RunButton onClick={() => runRoutine.mutate()} disabled={runRoutine.isPending} />
|
||||||
<PauseResumeButton
|
<div className="flex items-center gap-2">
|
||||||
isPaused={routine.status === "paused"}
|
<span className="text-xs font-medium uppercase tracking-[0.18em] text-muted-foreground">Automation</span>
|
||||||
onPause={() => updateRoutineStatus.mutate("paused")}
|
<div className="flex items-center gap-3">
|
||||||
onResume={() => updateRoutineStatus.mutate("active")}
|
<button
|
||||||
disabled={updateRoutineStatus.isPending || routine.status === "archived"}
|
type="button"
|
||||||
/>
|
role="switch"
|
||||||
|
aria-checked={automationEnabled}
|
||||||
|
aria-label={automationEnabled ? "Pause automatic triggers" : "Enable automatic triggers"}
|
||||||
|
disabled={automationToggleDisabled}
|
||||||
|
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||||||
|
automationEnabled ? "bg-emerald-500" : "bg-muted"
|
||||||
|
} ${automationToggleDisabled ? "cursor-not-allowed opacity-50" : ""}`}
|
||||||
|
onClick={() => updateRoutineStatus.mutate(automationEnabled ? "paused" : "active")}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`inline-block h-5 w-5 rounded-full bg-background shadow-sm transition-transform ${
|
||||||
|
automationEnabled ? "translate-x-5" : "translate-x-0.5"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<span className={`min-w-[3.75rem] text-sm font-medium ${automationLabelClassName}`}>
|
||||||
|
{automationLabel}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -953,9 +985,12 @@ export function RoutineDetail() {
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="runs" className="space-y-4">
|
<TabsContent value="runs" className="space-y-4">
|
||||||
{routine?.activeIssue && (
|
{hasLiveRun && (() => {
|
||||||
<LiveRunWidget issueId={routine.activeIssue.id} companyId={routine.companyId} />
|
const liveRun = (routineRuns ?? []).find((r) => r.status === "received" || r.status === "issue_created");
|
||||||
)}
|
const issueId = liveRun?.linkedIssue?.id ?? routine?.activeIssue?.id;
|
||||||
|
if (!issueId || !routine) return null;
|
||||||
|
return <LiveRunWidget issueId={issueId} companyId={routine.companyId} />;
|
||||||
|
})()}
|
||||||
{(routineRuns ?? []).length === 0 ? (
|
{(routineRuns ?? []).length === 0 ? (
|
||||||
<p className="text-xs text-muted-foreground">No runs yet.</p>
|
<p className="text-xs text-muted-foreground">No runs yet.</p>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue