Files
cim_summary/.planning/milestones/v1.0-phases/04-frontend/04-01-PLAN.md
admin 38a0f0619d chore: complete v1.0 Analytics & Monitoring milestone
Archive milestone artifacts (roadmap, requirements, audit, phase directories)
to .planning/milestones/. Evolve PROJECT.md with validated requirements and
decision outcomes. Create MILESTONES.md and RETROSPECTIVE.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 10:34:18 -05:00

240 lines
9.8 KiB
Markdown

---
phase: 04-frontend
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- frontend/src/services/adminService.ts
- frontend/src/components/AlertBanner.tsx
- frontend/src/components/AdminMonitoringDashboard.tsx
autonomous: true
requirements:
- ALRT-03
- ANLY-02
- HLTH-01
must_haves:
truths:
- "adminService exposes typed methods for getHealth(), getAnalytics(range), getAlerts(), and acknowledgeAlert(id)"
- "AlertBanner component renders critical active alerts with acknowledge button"
- "AdminMonitoringDashboard component shows health status grid and analytics summary with range selector"
artifacts:
- path: "frontend/src/services/adminService.ts"
provides: "Monitoring API client methods with typed interfaces"
contains: "getHealth"
- path: "frontend/src/components/AlertBanner.tsx"
provides: "Global alert banner with acknowledge callback"
exports: ["AlertBanner"]
- path: "frontend/src/components/AdminMonitoringDashboard.tsx"
provides: "Health panel + analytics summary panel"
exports: ["AdminMonitoringDashboard"]
key_links:
- from: "frontend/src/components/AlertBanner.tsx"
to: "adminService.ts"
via: "AlertEvent type import"
pattern: "import.*AlertEvent.*adminService"
- from: "frontend/src/components/AdminMonitoringDashboard.tsx"
to: "adminService.ts"
via: "getHealth and getAnalytics calls"
pattern: "adminService\\.(getHealth|getAnalytics)"
---
<objective>
Create the three building blocks for Phase 4 frontend: extend adminService with typed monitoring API methods, build the AlertBanner component, and build the AdminMonitoringDashboard component.
Purpose: These components and service methods are the foundation that Plan 02 wires into the Dashboard. Separating creation from wiring keeps each plan focused.
Output: Three files ready to be imported and mounted in App.tsx.
</objective>
<execution_context>
@/home/jonathan/.claude/get-shit-done/workflows/execute-plan.md
@/home/jonathan/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-frontend/04-RESEARCH.md
@frontend/src/services/adminService.ts
@frontend/src/components/Analytics.tsx
@frontend/src/components/UploadMonitoringDashboard.tsx
@frontend/src/utils/cn.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: Extend adminService with monitoring API methods and types</name>
<files>frontend/src/services/adminService.ts</files>
<action>
Add three exported interfaces and four new methods to the existing AdminService class in `frontend/src/services/adminService.ts`.
**Interfaces to add** (above the AdminService class):
```typescript
export interface AlertEvent {
id: string;
service_name: string;
alert_type: 'service_down' | 'service_degraded' | 'recovery';
status: 'active' | 'acknowledged' | 'resolved';
message: string | null;
details: Record<string, unknown> | null;
created_at: string;
acknowledged_at: string | null;
resolved_at: string | null;
}
export interface ServiceHealthEntry {
service: string;
status: 'healthy' | 'degraded' | 'down' | 'unknown';
checkedAt: string | null;
latencyMs: number | null;
errorMessage: string | null;
}
export interface AnalyticsSummary {
range: string;
totalUploads: number;
succeeded: number;
failed: number;
successRate: number;
avgProcessingMs: number | null;
generatedAt: string;
}
```
**IMPORTANT type casing note** (from RESEARCH Pitfall 4):
- `ServiceHealthEntry` uses camelCase (backend admin.ts remaps to camelCase)
- `AlertEvent` uses snake_case (backend returns raw model data)
- `AnalyticsSummary` uses camelCase (from backend analyticsService.ts)
**Methods to add** inside the AdminService class:
```typescript
async getHealth(): Promise<ServiceHealthEntry[]> {
const response = await apiClient.get('/admin/health');
return response.data.data;
}
async getAnalytics(range: string = '24h'): Promise<AnalyticsSummary> {
const response = await apiClient.get(`/admin/analytics?range=${range}`);
return response.data.data;
}
async getAlerts(): Promise<AlertEvent[]> {
const response = await apiClient.get('/admin/alerts');
return response.data.data;
}
async acknowledgeAlert(id: string): Promise<AlertEvent> {
const response = await apiClient.post(`/admin/alerts/${id}/acknowledge`);
return response.data.data;
}
```
Keep all existing methods and interfaces. Do not modify the `apiClient` interceptor or the `ADMIN_EMAIL` check.
</action>
<verify>
<automated>cd /home/jonathan/Coding/cim_summary/frontend && npx tsc --noEmit --strict src/services/adminService.ts 2>&1 | head -20</automated>
<manual>Verify the file exports AlertEvent, ServiceHealthEntry, AnalyticsSummary interfaces and the four new methods</manual>
</verify>
<done>adminService.ts exports 3 new typed interfaces and 4 new methods (getHealth, getAnalytics, getAlerts, acknowledgeAlert) alongside all existing functionality</done>
</task>
<task type="auto">
<name>Task 2: Create AlertBanner and AdminMonitoringDashboard components</name>
<files>frontend/src/components/AlertBanner.tsx, frontend/src/components/AdminMonitoringDashboard.tsx</files>
<action>
**AlertBanner.tsx** — Create a new component at `frontend/src/components/AlertBanner.tsx`:
Props interface:
```typescript
interface AlertBannerProps {
alerts: AlertEvent[];
onAcknowledge: (id: string) => Promise<void>;
}
```
Behavior:
- Filter alerts to show only `status === 'active'` AND `alert_type` is `service_down` or `service_degraded` (per RESEARCH Pitfall — `recovery` is informational, not critical)
- If no critical alerts after filtering, return `null`
- Render a red banner (`bg-red-600 px-4 py-3`) with each alert showing:
- `AlertTriangle` icon from lucide-react (h-5 w-5, flex-shrink-0)
- Text: `{alert.service_name}: {alert.message ?? alert.alert_type}` (text-sm font-medium text-white)
- "Acknowledge" button with `X` icon from lucide-react (text-sm underline hover:no-underline)
- `onAcknowledge` called with `alert.id` on button click
- Import `AlertEvent` from `../services/adminService`
- Import `cn` from `../utils/cn`
- Use `AlertTriangle` and `X` from `lucide-react`
**AdminMonitoringDashboard.tsx** — Create at `frontend/src/components/AdminMonitoringDashboard.tsx`:
This component contains two sections: Service Health Panel and Processing Analytics Panel.
State:
```typescript
const [health, setHealth] = useState<ServiceHealthEntry[]>([]);
const [analytics, setAnalytics] = useState<AnalyticsSummary | null>(null);
const [range, setRange] = useState('24h');
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
```
Data fetching: Use `useCallback` + `useEffect` pattern matching existing `Analytics.tsx`:
- `loadData()` calls `Promise.all([adminService.getHealth(), adminService.getAnalytics(range)])`
- Sets loading/error state appropriately
- Re-fetches when `range` changes
**Service Health Panel:**
- 2x2 grid on desktop (`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4`)
- Each card: white bg, rounded-lg, shadow-soft, border border-gray-100, p-4
- Status dot: `w-3 h-3 rounded-full` with color mapping:
- `healthy``bg-green-500`
- `degraded``bg-yellow-500`
- `down``bg-red-500`
- `unknown``bg-gray-400`
- Service display name mapping: `document_ai` → "Document AI", `llm_api` → "LLM API", `supabase` → "Supabase", `firebase_auth` → "Firebase Auth"
- Show `checkedAt` as `new Date(checkedAt).toLocaleString()` if available, otherwise "Never checked"
- Show `latencyMs` with "ms" suffix if available
- Use `Activity`, `Clock` icons from lucide-react
**Processing Analytics Panel:**
- Range selector: `<select>` with options `24h`, `7d`, `30d` — onChange updates `range` state
- Stat cards in 1x5 grid: Total Uploads, Succeeded, Failed, Success Rate (formatted as `(successRate * 100).toFixed(1)%`), Avg Processing Time (format `avgProcessingMs` as seconds: `(avgProcessingMs / 1000).toFixed(1)s`, or "N/A" if null)
- Include a "Refresh" button that calls `loadData()` — matches existing Analytics.tsx refresh pattern
- Use `RefreshCw` icon from lucide-react for refresh button
Loading state: Show `animate-spin rounded-full h-8 w-8 border-b-2 border-accent-500` centered (matching existing App.tsx pattern).
Error state: Show error message with retry button.
Import `ServiceHealthEntry`, `AnalyticsSummary` from `../services/adminService`.
Import `adminService` from `../services/adminService`.
Import `cn` from `../utils/cn`.
</action>
<verify>
<automated>cd /home/jonathan/Coding/cim_summary/frontend && npx tsc --noEmit 2>&1 | head -30</automated>
<manual>Check that AlertBanner.tsx and AdminMonitoringDashboard.tsx exist and export their components</manual>
</verify>
<done>AlertBanner renders critical alerts with acknowledge buttons; AdminMonitoringDashboard renders health status grid with colored dots and analytics summary with range selector and refresh</done>
</task>
</tasks>
<verification>
- `npx tsc --noEmit` passes with no type errors in the three modified/created files
- AlertBanner exports a React component accepting `alerts` and `onAcknowledge` props
- AdminMonitoringDashboard exports a React component with no required props
- adminService exports AlertEvent, ServiceHealthEntry, AnalyticsSummary interfaces
- adminService.getHealth(), getAnalytics(), getAlerts(), acknowledgeAlert() methods exist with correct return types
</verification>
<success_criteria>
All three files compile without TypeScript errors. Components follow existing project patterns (Tailwind, lucide-react, cn utility). Types match backend API response shapes exactly (camelCase for health/analytics, snake_case for alerts).
</success_criteria>
<output>
After completion, create `.planning/phases/04-frontend/04-01-SUMMARY.md`
</output>