From 1239b853d3954a29a324cc3d03c2d3646fea6494 Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Thu, 5 Mar 2026 12:14:33 +0000 Subject: [PATCH 1/3] feat: add pipeline logs panel to editor and metrics pages Wire up the existing PipelineLogs component to both the pipeline metrics page (as a new Logs card) and the pipeline editor (as a collapsible bottom panel toggled from the toolbar). The toolbar shows a red dot indicator when recent errors exist, helping users spot pipeline issues during initial configuration. --- .../pipelines/[id]/metrics/page.tsx | 11 ++++++++ src/app/(dashboard)/pipelines/[id]/page.tsx | 19 +++++++++++++ src/components/flow/flow-toolbar.tsx | 27 +++++++++++++++++++ src/components/pipeline/pipeline-logs.tsx | 8 ++++-- 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/app/(dashboard)/pipelines/[id]/metrics/page.tsx b/src/app/(dashboard)/pipelines/[id]/metrics/page.tsx index 7118b01..8a21272 100644 --- a/src/app/(dashboard)/pipelines/[id]/metrics/page.tsx +++ b/src/app/(dashboard)/pipelines/[id]/metrics/page.tsx @@ -9,6 +9,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { SummaryCards } from "@/components/metrics/summary-cards"; import { MetricsChart } from "@/components/metrics/component-chart"; +import { PipelineLogs } from "@/components/pipeline/pipeline-logs"; const TIME_RANGES = [ { label: "5m", minutes: 5 }, @@ -116,6 +117,16 @@ export default function PipelineMetricsPage() { )} + + {/* Pipeline Logs */} + + + Logs + + + + + ); } diff --git a/src/app/(dashboard)/pipelines/[id]/page.tsx b/src/app/(dashboard)/pipelines/[id]/page.tsx index db87144..89849f9 100644 --- a/src/app/(dashboard)/pipelines/[id]/page.tsx +++ b/src/app/(dashboard)/pipelines/[id]/page.tsx @@ -34,6 +34,7 @@ import { DeployDialog } from "@/components/flow/deploy-dialog"; import { SaveTemplateDialog } from "@/components/flow/save-template-dialog"; import { ConfirmDialog } from "@/components/confirm-dialog"; import { PipelineMetricsChart } from "@/components/pipeline/metrics-chart"; +import { PipelineLogs } from "@/components/pipeline/pipeline-logs"; function aggregateProcessStatus( statuses: Array<{ status: string }> @@ -114,6 +115,7 @@ function PipelineBuilderInner({ pipelineId }: { pipelineId: string }) { const [deleteOpen, setDeleteOpen] = useState(false); const [undeployOpen, setUndeployOpen] = useState(false); const [metricsOpen, setMetricsOpen] = useState(false); + const [logsOpen, setLogsOpen] = useState(false); const loadGraph = useFlowStore((s) => s.loadGraph); const isDirty = useFlowStore((s) => s.isDirty); @@ -157,6 +159,15 @@ function PipelineBuilderInner({ pipelineId }: { pipelineId: string }) { ), ); + // Lightweight check for recent errors (for toolbar badge) + const recentErrorsQuery = useQuery( + trpc.pipeline.logs.queryOptions( + { pipelineId, levels: ["ERROR"], limit: 1 }, + { enabled: !!isDeployed && !logsOpen, refetchInterval: 10000 }, + ), + ); + const hasRecentErrors = (recentErrorsQuery.data?.items?.length ?? 0) > 0; + // Merge component metrics into flow node data useEffect(() => { const components = componentMetricsQuery.data?.components; @@ -367,6 +378,9 @@ function PipelineBuilderInner({ pipelineId }: { pipelineId: string }) { isDirty={isDirty} metricsOpen={metricsOpen} onToggleMetrics={() => setMetricsOpen((v) => !v)} + logsOpen={logsOpen} + onToggleLogs={() => setLogsOpen((v) => !v)} + hasRecentErrors={hasRecentErrors} processStatus={ pipelineQuery.data?.nodeStatuses ? aggregateProcessStatus(pipelineQuery.data.nodeStatuses) @@ -417,6 +431,11 @@ function PipelineBuilderInner({ pipelineId }: { pipelineId: string }) { )} + {logsOpen && ( +
+ +
+ )} void; + logsOpen?: boolean; + onToggleLogs?: () => void; + hasRecentErrors?: boolean; processStatus?: ProcessStatusValue | null; } @@ -87,6 +91,9 @@ export function FlowToolbar({ isDirty = false, metricsOpen = false, onToggleMetrics, + logsOpen = false, + onToggleLogs, + hasRecentErrors = false, processStatus, }: FlowToolbarProps) { const globalConfig = useFlowStore((s) => s.globalConfig); @@ -317,6 +324,26 @@ export function FlowToolbar({ )} + {onToggleLogs && ( + + + + + {logsOpen ? "Hide logs" : "Show logs"} + + )} + diff --git a/src/components/pipeline/pipeline-logs.tsx b/src/components/pipeline/pipeline-logs.tsx index 53f5b9c..9d7e9b8 100644 --- a/src/components/pipeline/pipeline-logs.tsx +++ b/src/components/pipeline/pipeline-logs.tsx @@ -166,8 +166,12 @@ export function PipelineLogs({ pipelineId, nodeId }: PipelineLogsProps) { {log.level} {" "} - [{log.node.name}] - {" "} + {log.node?.name && ( + <> + [{log.node.name}] + {" "} + + )} {log.message} ))} From f6bd90ca1b3a7ed976343f1e561dca68eb740e9d Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Thu, 5 Mar 2026 12:36:22 +0000 Subject: [PATCH 2/3] fix: address greptile review findings --- src/app/(dashboard)/pipelines/[id]/metrics/page.tsx | 4 +++- src/app/(dashboard)/pipelines/[id]/page.tsx | 4 ++-- src/server/routers/pipeline.ts | 6 +++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/app/(dashboard)/pipelines/[id]/metrics/page.tsx b/src/app/(dashboard)/pipelines/[id]/metrics/page.tsx index 8a21272..45517b4 100644 --- a/src/app/(dashboard)/pipelines/[id]/metrics/page.tsx +++ b/src/app/(dashboard)/pipelines/[id]/metrics/page.tsx @@ -124,7 +124,9 @@ export default function PipelineMetricsPage() { Logs - +
+ +
diff --git a/src/app/(dashboard)/pipelines/[id]/page.tsx b/src/app/(dashboard)/pipelines/[id]/page.tsx index 89849f9..245e924 100644 --- a/src/app/(dashboard)/pipelines/[id]/page.tsx +++ b/src/app/(dashboard)/pipelines/[id]/page.tsx @@ -159,10 +159,10 @@ function PipelineBuilderInner({ pipelineId }: { pipelineId: string }) { ), ); - // Lightweight check for recent errors (for toolbar badge) + // Lightweight check for recent errors (for toolbar badge) — 24h window const recentErrorsQuery = useQuery( trpc.pipeline.logs.queryOptions( - { pipelineId, levels: ["ERROR"], limit: 1 }, + { pipelineId, levels: ["ERROR"], limit: 1, since: new Date(Date.now() - 24 * 60 * 60 * 1000) }, { enabled: !!isDeployed && !logsOpen, refetchInterval: 10000 }, ), ); diff --git a/src/server/routers/pipeline.ts b/src/server/routers/pipeline.ts index 4841761..346110c 100644 --- a/src/server/routers/pipeline.ts +++ b/src/server/routers/pipeline.ts @@ -822,11 +822,12 @@ export const pipelineRouter = router({ limit: z.number().min(1).max(500).default(200), levels: z.array(z.nativeEnum(LogLevel)).optional(), nodeId: z.string().optional(), + since: z.date().optional(), }), ) .use(withTeamAccess("VIEWER")) .query(async ({ input }) => { - const { pipelineId, cursor, limit, levels, nodeId } = input; + const { pipelineId, cursor, limit, levels, nodeId, since } = input; const take = limit; const where: Record = { pipelineId }; @@ -836,6 +837,9 @@ export const pipelineRouter = router({ if (nodeId) { where.nodeId = nodeId; } + if (since) { + where.timestamp = { gte: since }; + } const items = await prisma.pipelineLog.findMany({ where, From ea53476f50280f3619e8ce15fb8a46ec637f908c Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Thu, 5 Mar 2026 12:54:08 +0000 Subject: [PATCH 3/3] fix: stabilize error badge query key with useMemo --- src/app/(dashboard)/pipelines/[id]/page.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app/(dashboard)/pipelines/[id]/page.tsx b/src/app/(dashboard)/pipelines/[id]/page.tsx index 245e924..49bb9bd 100644 --- a/src/app/(dashboard)/pipelines/[id]/page.tsx +++ b/src/app/(dashboard)/pipelines/[id]/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useParams, useRouter } from "next/navigation"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import type { NodeMetricsData } from "@/stores/flow-store"; @@ -160,9 +160,14 @@ function PipelineBuilderInner({ pipelineId }: { pipelineId: string }) { ); // Lightweight check for recent errors (for toolbar badge) — 24h window + const errorCheckSince = useMemo( + () => new Date(Date.now() - 24 * 60 * 60 * 1000), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); const recentErrorsQuery = useQuery( trpc.pipeline.logs.queryOptions( - { pipelineId, levels: ["ERROR"], limit: 1, since: new Date(Date.now() - 24 * 60 * 60 * 1000) }, + { pipelineId, levels: ["ERROR"], limit: 1, since: errorCheckSince }, { enabled: !!isDeployed && !logsOpen, refetchInterval: 10000 }, ), );