From 94aa5b2efa088e8bd39da770354710ec61b30762 Mon Sep 17 00:00:00 2001 From: Nasyarobby Putra Date: Fri, 14 Aug 2026 21:14:07 +0700 Subject: [PATCH] feat(web): enhance LogViewer and EventDetailPage with filtering functionality - Updated LogViewer component to accept filters and a callback for removing filters. - Modified EventDetailPage to manage step filters, allowing users to filter logs based on selected steps. - Added UI elements for displaying active filters and a button to toggle filtering for each step. - Improved log visibility by applying filters dynamically based on user selection. --- packages/web/src/components/LogViewer.jsx | 20 ++++++++- packages/web/src/pages/EventDetailPage.jsx | 50 ++++++++++++++++++++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/packages/web/src/components/LogViewer.jsx b/packages/web/src/components/LogViewer.jsx index 85dfb57..ddd8ac5 100644 --- a/packages/web/src/components/LogViewer.jsx +++ b/packages/web/src/components/LogViewer.jsx @@ -1,7 +1,8 @@ import { useState } from "react"; +import { LuX } from "react-icons/lu"; import { levelName } from "../lib/format.jsx"; -export function LogViewer({ logs = [], className = "" }) { +export function LogViewer({ logs = [], filters = [], onRemoveFilter, className = "" }) { const [wordWrap, setWordWrap] = useState(true); return ( @@ -18,6 +19,23 @@ export function LogViewer({ logs = [], className = "" }) { /> + {filters.length > 0 ? ( +
+ {filters.map((f) => ( + + {f.label} + + + ))} +
+ ) : null}
{logs.length === 0 ? (
diff --git a/packages/web/src/pages/EventDetailPage.jsx b/packages/web/src/pages/EventDetailPage.jsx index 9b3bd31..1052f68 100644 --- a/packages/web/src/pages/EventDetailPage.jsx +++ b/packages/web/src/pages/EventDetailPage.jsx @@ -1,5 +1,6 @@ +import { useMemo, useState } from "react"; import { Link, useParams } from "react-router-dom"; -import { LuArrowLeft } from "react-icons/lu"; +import { LuArrowLeft, LuFilter } from "react-icons/lu"; import { useRun } from "../api/hooks.js"; import { LogViewer } from "../components/LogViewer.jsx"; import { formatTime, StatusBadge } from "../lib/format.jsx"; @@ -11,6 +12,10 @@ function stepLabel(s) { return s.script; } +function filterLabel(s) { + return `#${s.step_index} ${stepLabel(s)}`; +} + function isEditableScript(s) { return Boolean(s.script) && s.script !== "set"; } @@ -18,6 +23,29 @@ function isEditableScript(s) { export function EventDetailPage() { const { id } = useParams(); const { data: run, isLoading, error } = useRun(id); + const [stepFilters, setStepFilters] = useState([]); + + const steps = run?.steps ?? []; + const logs = run?.logs ?? []; + const logFilters = useMemo( + () => + stepFilters + .map((stepId) => steps.find((s) => s.id === stepId)) + .filter(Boolean) + .map((s) => ({ id: s.id, label: filterLabel(s) })), + [stepFilters, steps], + ); + const visibleLogs = useMemo(() => { + if (stepFilters.length === 0) return logs; + const ids = new Set(stepFilters); + return logs.filter((l) => ids.has(l.step_id)); + }, [logs, stepFilters]); + + function toggleStepFilter(stepId) { + setStepFilters((prev) => + prev.includes(stepId) ? prev.filter((id) => id !== stepId) : [...prev, stepId], + ); + } if (isLoading) return ; if (error || !run) return

Event not found

; @@ -71,10 +99,11 @@ export function EventDetailPage() { Status Duration Error + - {(run.steps ?? []).map((s) => ( + {steps.map((s) => ( {s.step_index} @@ -91,6 +120,17 @@ export function EventDetailPage() { {s.duration_ms != null ? `${s.duration_ms}ms` : "—"} {s.error || ""} + + + ))} @@ -98,7 +138,11 @@ export function EventDetailPage() {
- + toggleStepFilter(stepId)} + />
); }