From bd0493aadee0961710c1a70caa711e2d8eb27454 Mon Sep 17 00:00:00 2001 From: kitos Date: Fri, 29 May 2026 11:33:55 +0200 Subject: [PATCH] fix(ui): make all Jira and time panels read-only everywhere MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WorklogTimeline: add readOnly prop — hides 'Log Time' button and form. TestPhaseTimeline: remove 'Sync to Tempo' button from TempoSyncBadge; only displays the green 'Tempo' badge when already synced. Cleans up unused imports (useState, useMutation, useQueryClient, syncTestToTempo). CampaignDetailPage: JiraLinkPanel and WorklogTimeline both now rendered with readOnly=true; JiraLinkPanel receives campaign name as label. Jira tickets and time worklogs are created automatically by the system (campaign activation, test workflow) — no manual editing from detail pages. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/TestPhaseTimeline.tsx | 90 +++---------------- frontend/src/components/WorklogTimeline.tsx | 38 ++++---- frontend/src/pages/CampaignDetailPage.tsx | 6 +- 3 files changed, 36 insertions(+), 98 deletions(-) diff --git a/frontend/src/components/TestPhaseTimeline.tsx b/frontend/src/components/TestPhaseTimeline.tsx index 561a039..3336d80 100644 --- a/frontend/src/components/TestPhaseTimeline.tsx +++ b/frontend/src/components/TestPhaseTimeline.tsx @@ -1,8 +1,7 @@ /** * TestPhaseTimeline * - * Read-only timeline of automated phase durations, with Tempo sync status and - * a manual "Sync to Tempo" button for the Red Team Execution phase. + * Read-only timeline of automated phase durations with Tempo sync status. * * 1. Red Team Execution (red_started_at → blue_started_at, minus paused) * 2. Blue Queue (blue_started_at → blue_work_started_at) @@ -11,14 +10,12 @@ * 5. Blue Lead Validation (blue_validated_at + status) */ -import { useState } from "react"; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; import { - Clock, Sword, Shield, CheckCircle, XCircle, Timer, RefreshCw, Loader2, + Clock, Sword, Shield, CheckCircle, XCircle, Timer, } from "lucide-react"; import type { Test } from "../types/models"; import { listTestWorklogs, type Worklog } from "../api/worklogs"; -import { syncTestToTempo } from "../api/tests"; // ── Helpers ────────────────────────────────────────────────────────── @@ -118,71 +115,18 @@ function ValidationBadge({ status }: { status: string | null }) { function TempoSyncBadge({ worklog, - testId, - onSynced, }: { worklog: Worklog | undefined; - testId: string; - onSynced: () => void; }) { - const [toast, setToast] = useState(null); + if (!worklog?.tempo_synced) return null; - const mutation = useMutation({ - mutationFn: () => syncTestToTempo(testId), - onSuccess: (data) => { - const r = data.results[0]; - if (r?.status === "synced") { - setToast("Synced to Tempo ✓"); - onSynced(); - } else if (r?.status === "already_synced") { - setToast("Already synced"); - } else { - setToast(r?.detail ?? "Skipped — check Tempo settings"); - } - setTimeout(() => setToast(null), 4000); - }, - onError: (e: unknown) => { - const msg = - e && typeof e === "object" && "response" in e - ? ((e as { response?: { data?: { detail?: string } } }).response?.data?.detail ?? "Sync failed") - : "Sync failed"; - setToast(msg); - setTimeout(() => setToast(null), 5000); - }, - }); - - // Already synced - if (worklog?.tempo_synced) { - return ( - - Tempo - - ); - } - - // Not synced — show button return ( -
- - {toast && ( - {toast} - )} -
+ + Tempo + ); } @@ -194,8 +138,6 @@ interface TestPhaseTimelineProps { } export default function TestPhaseTimeline({ test, testId }: TestPhaseTimelineProps) { - const queryClient = useQueryClient(); - const { red_started_at, blue_started_at, @@ -217,10 +159,6 @@ export default function TestPhaseTimeline({ test, testId }: TestPhaseTimelinePro const redWorklog = worklogs.find((w) => w.activity_type === "red_team_execution"); - const refreshWorklogs = () => { - if (testId) queryClient.invalidateQueries({ queryKey: ["worklogs", "test", testId] }); - }; - // Compute per-phase durations const redExecSecs = red_started_at && blue_started_at @@ -270,12 +208,8 @@ export default function TestPhaseTimeline({ test, testId }: TestPhaseTimelinePro duration={redExecSecs} isLast={++phaseIdx === lastIdx} extra={ - testId ? ( - + redWorklog ? ( + ) : undefined } /> diff --git a/frontend/src/components/WorklogTimeline.tsx b/frontend/src/components/WorklogTimeline.tsx index 56851e7..35e51dc 100644 --- a/frontend/src/components/WorklogTimeline.tsx +++ b/frontend/src/components/WorklogTimeline.tsx @@ -7,6 +7,8 @@ import { useAuth } from "../context/AuthContext"; interface WorklogTimelineProps { entityType: string; entityId: string; + /** When true, hides the Log Time button and form (read-only display). */ + readOnly?: boolean; } const activityColors: Record = { @@ -36,7 +38,7 @@ function formatDate(dateStr: string): string { }); } -export default function WorklogTimeline({ entityType, entityId }: WorklogTimelineProps) { +export default function WorklogTimeline({ entityType, entityId, readOnly = false }: WorklogTimelineProps) { const queryClient = useQueryClient(); const { user } = useAuth(); const [showForm, setShowForm] = useState(false); @@ -92,25 +94,27 @@ export default function WorklogTimeline({ entityType, entityId }: WorklogTimelin Total: {formatDuration(totalSeconds)} )} - + {!readOnly && ( + + )} - {/* New worklog form */} - {showForm && ( + {/* New worklog form — only in edit mode */} + {!readOnly && showForm && (
diff --git a/frontend/src/pages/CampaignDetailPage.tsx b/frontend/src/pages/CampaignDetailPage.tsx index 382df27..6beb018 100644 --- a/frontend/src/pages/CampaignDetailPage.tsx +++ b/frontend/src/pages/CampaignDetailPage.tsx @@ -629,10 +629,10 @@ export default function CampaignDetailPage() { )}
- {/* Jira & Worklogs */} + {/* Jira & Worklogs — read-only, automatically managed */}
- - + +
{/* Add Test to Campaign Modal */}