From c797a327f31f20a856b5ce9a0a5ef784736bc533 Mon Sep 17 00:00:00 2001 From: Mohammad Rad Date: Sun, 13 Oct 2024 17:35:05 -0700 Subject: [PATCH 1/9] Feat: add new component to view latest Build logs from github. Group the logs similar to how Github workflow demonstrate it --- app/(dashboard)/dashboard/log-view.tsx | 127 +++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 app/(dashboard)/dashboard/log-view.tsx diff --git a/app/(dashboard)/dashboard/log-view.tsx b/app/(dashboard)/dashboard/log-view.tsx new file mode 100644 index 00000000..71509de8 --- /dev/null +++ b/app/(dashboard)/dashboard/log-view.tsx @@ -0,0 +1,127 @@ +'use client' + +import { useRef, useMemo } from 'react' +import { Loader2, ChevronRight, ChevronDown } from 'lucide-react' +import { getWorkflowLogs } from '@/lib/github' +import useSWR from 'swr' +import { useState } from 'react' + +interface LogViewProps { + owner: string; + repo: string; + runId: string | null; +} + +interface LogGroup { + id: string; + name: string; + logs: string[]; +} + +export function LogView({ owner, repo, runId }: LogViewProps) { + const logContainerRef = useRef(null) + const [expandedGroups, setExpandedGroups] = useState>({}) + + const { data: logs, error, isLoading } = useSWR( + runId ? ['workflowLogs', owner, repo, runId] : null, + () => getWorkflowLogs(owner, repo, runId!), + { + revalidateOnFocus: false, + revalidateOnReconnect: false, + } + ) + + const parsedLogs = useMemo(() => { + if (!logs) return []; + + const groups: LogGroup[] = []; + let currentGroup: LogGroup | null = null; + const lines = logs.split('\n'); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].replace(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z\s*/, ''); + if (line.startsWith('File:')) { + if (currentGroup) { + groups.push(currentGroup); + } + currentGroup = { + id: `group-${groups.length}`, + name: line.trim(), + logs: [] + }; + } else if (currentGroup) { + currentGroup.logs.push(line); + } else { + if (!groups.length || groups[groups.length - 1].name !== 'Other') { + groups.push({ id: `group-${groups.length}`, name: 'Other', logs: [] }); + } + groups[groups.length - 1].logs.push(line); + } + } + + if (currentGroup) { + groups.push(currentGroup); + } + + // Add line numbers and trim group names + return groups.map(group => ({ + ...group, + name: group.name + .replace(/^File:\s*/, '') + .replace(/^.*?_/, '') + .replace(/\.txt$/, '') + .split('/')[0], + logs: group.logs.map((log, index) => `${(index + 1).toString().padStart(4, ' ')} | ${log}`) + })); + }, [logs]); + + const toggleGroup = (groupId: string) => { + setExpandedGroups(prev => ({ + ...prev, + [groupId]: !prev[groupId] + })); + }; + + if (isLoading) { + return ( +
+ + Loading logs... +
+ ) + } + + if (error) { + return
Error loading logs: {error.message}
+ } + + return ( +
+
+

Logs

+
+
+ {parsedLogs.map((group) => ( +
+ + {expandedGroups[group.id] && ( +
+                {group.logs.join('\n')}
+              
+ )} +
+ ))} +
+
+ ) +} From c18aa8872e50d312b6843a865ecd1169fdfbf524 Mon Sep 17 00:00:00 2001 From: Mohammad Rad Date: Sun, 13 Oct 2024 18:08:45 -0700 Subject: [PATCH 2/9] Update: integrate log-view component with the pull-request component to view logs --- app/(dashboard)/dashboard/pull-request.tsx | 48 +++++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/app/(dashboard)/dashboard/pull-request.tsx b/app/(dashboard)/dashboard/pull-request.tsx index 7f61faaa..4d39dae6 100644 --- a/app/(dashboard)/dashboard/pull-request.tsx +++ b/app/(dashboard)/dashboard/pull-request.tsx @@ -11,6 +11,8 @@ import { PlusCircle, Loader2, AlertCircle, + ChevronDown, + ChevronUp, } from "lucide-react"; import Link from "next/link"; import { Checkbox } from "@/components/ui/checkbox"; @@ -22,6 +24,9 @@ import { commitChangesToPullRequest, getPullRequestInfo, getFailingTests } from import { Input } from "@/components/ui/input"; import useSWR from 'swr'; import { fetchBuildStatus } from '@/lib/github'; +import { LogView } from './log-view' +import { getLatestRunId } from '@/lib/github' +import { cn } from "@/lib/utils" const ReactDiffViewer = dynamic(() => import("react-diff-viewer"), { ssr: false, @@ -33,13 +38,14 @@ interface PullRequestItemProps { export function PullRequestItem({ pullRequest: initialPullRequest }: PullRequestItemProps) { const [optimisticRunning, setOptimisticRunning] = useState(false); + const [showLogs, setShowLogs] = useState(false); const { data: pullRequest, mutate } = useSWR( `pullRequest-${initialPullRequest.id}`, () => fetchBuildStatus(initialPullRequest.repository.owner.login, initialPullRequest.repository.name, initialPullRequest.number), { fallbackData: initialPullRequest, - refreshInterval: optimisticRunning ? 10000 : 0, // Poll every 5 seconds when optimisticRunning is true + refreshInterval: optimisticRunning ? 10000 : 0, onSuccess: (data) => { if (data.buildStatus !== "running" && data.buildStatus !== "pending") { setOptimisticRunning(false); @@ -48,6 +54,13 @@ export function PullRequestItem({ pullRequest: initialPullRequest }: PullRequest } ); + const { data: latestRunId } = useSWR( + pullRequest.buildStatus === 'success' || pullRequest.buildStatus === 'failure' + ? ['latestRunId', pullRequest.repository.owner.login, pullRequest.repository.name, pullRequest.branchName] + : null, + () => getLatestRunId(pullRequest.repository.owner.login, pullRequest.repository.name, pullRequest.branchName) + ); + const [testFiles, setTestFiles] = useState([]); const [selectedFiles, setSelectedFiles] = useState>({}); const [expandedFiles, setExpandedFiles] = useState>({}); @@ -253,6 +266,28 @@ export function PullRequestItem({ pullRequest: initialPullRequest }: PullRequest > Build: {isRunning ? "Running" : isPending ? "Pending" : pullRequest.buildStatus} + {(pullRequest.buildStatus === 'success' || pullRequest.buildStatus === 'failure') && latestRunId && ( + + )} {testFiles.length > 0 ? (