React应用无法获取Jenkins Pipeline状态及相关问题求助
问题定位与解决请求
我正在开发一个React应用,通过Jenkins Pipeline完成以下流程:克隆代码仓库、运行MegaLinter,流程结束后清理megalinter.log文件并在Issues.jsx页面展示检测出的问题。Pipeline构建过程中会显示动画状态,但遇到了以下问题:
- 即使Pipeline触发成功,几秒后控制台仍出现错误;
- 构建完成后无法获取工件;
- 动画状态很快显示失败,但Jenkins中Pipeline已正常触发,不清楚错误原因。
另外,我未使用包含147的URL,但该请求出现错误。以下是相关代码,寻求问题定位与解决方法。
Jenkins Pipeline代码
pipeline { agent any environment { NODEJS_HOME = tool name: 'NodeJS', type: 'jenkins.plugins.nodejs.tools.NodeJSInstallation' PATH = "${env.NODEJS_HOME}/bin:${env.PATH}" } parameters { string(name: 'REPO_URL', defaultValue: '', description: 'URL of the Git repository to analyze') } stages { stage('Clone Repository') { steps { script { try { git url: "${params.REPO_URL}", branch: 'main' } catch (Exception e) { echo "Main branch not found, trying master branch" git url: "${params.REPO_URL}", branch: 'master' } } } } stage('Run MegaLinter') { steps { script { try { bat "npx mega-linter-runner --flavor cupcake --release v6" } catch (Exception e) { echo "MegaLinter found issues, but continuing pipeline" } } } } } post { always { archiveArtifacts artifacts: 'megalinter-reports/**', allowEmptyArchive: true cleanWs() } } }
Issues.jsx页面代码
const Issues = () => { const { repoName } = useParams(); const [buildStage, setBuildStage] = useState('Initializing'); const [issues, setIssues] = useState([]); const [filteredIssues, setFilteredIssues] = useState([]); const [filters, setFilters] = useState({ severity: "", language: "", linter: "" }); useEffect(() => { let isMounted = true; let intervalId; const runPipeline = async () => { try { setBuildStage('Triggering Pipeline'); await triggerPipeline({ name: repoName }); if (!isMounted) return; setBuildStage('Waiting for Build'); let lastBuildNumber = null; const pollBuildStatus = async () => { try { const { isBuilding, result, number } = await checkBuildStatus(); if (number !== lastBuildNumber) { lastBuildNumber = number; console.log(`New build started: #${number}`); } if (!isBuilding && result) { clearInterval(intervalId); if (result === 'SUCCESS') { if (isMounted) { setBuildStage('Fetching Results'); const results = await getBuildResults(number); await processMegaLinterResults(results.megaLinterLog); setBuildStage('Complete'); } } else { if (isMounted) setBuildStage(`Failed: ${result}`); } } } catch (error) { console.error('Error in pollBuildStatus:', error); if (isMounted) setBuildStage('Failed: Error checking status'); } }; intervalId = setInterval(pollBuildStatus, 5000); // Check every 5 seconds } catch (error) { console.error('Error in pipeline process:', error); if (isMounted) setBuildStage('Failed: Error triggering pipeline'); } }; runPipeline(); return () => { isMounted = false; if (intervalId) clearInterval(intervalId); }; }, [repoName]); const processMegaLinterResults = (logContent) => { try { const processedIssues = []; const lines = logContent.split('\n'); let currentLinter = ''; let currentFile = ''; for (const line of lines) { if (line.startsWith('Linter:')) { currentLinter = line.split(':')[1].trim(); } else if (line.startsWith('File:')) { currentFile = line.split(':')[1].trim(); } else if (line.includes('|')) { const [lineNumber, column, level, message] = line.split('|').map(item => item.trim()); processedIssues.push({ message, linter: currentLinter, file: currentFile, severity: level, line: lineNumber, column }); } } setIssues(processedIssues); setFilteredIssues(processedIssues); } catch (error) { console.error('Error processing MegaLinter results:', error); } }; useEffect(() => { const filtered = issues.filter(issue => (!filters.severity || issue.severity === filters.severity) && (!filters.language || issue.language === filters.language) && (!filters.linter || issue.linter === filters.linter) ); setFilteredIssues(filtered); }, [issues, filters]); const handleFilterChange = (filterType, value) => { setFilters(prev => ({ ...prev, [filterType]: value })); }; return ( <div className='flex'> <Sidebar> <SideBarItem link="/user/dashboard" icon={<Home size={20} />} text="Home" alert /> <SideBarItem link={`/user/dashboard/repos/${repoName}`} icon={<BarChart4 size={20} />} text="Details" alert /> <SideBarItem link={`/user/dashboard/repos/${repoName}/issues`} icon={<Bug size={20} />} text="Issues" /> <SideBarItem link={`/user/dashboard/repos/${repoName}/commits`} icon={<GitCommitVertical size={20} />} text="Commits" /> <SideBarItem link={`/user/dashboard/repos/${repoName}/pull-requests`} icon={<GitPullRequestIcon size={20} />} text="Pull Requests" /> <SideBarItem link="/user/dashboard/help" icon={<HandHelping size={20} />} text="Help" /> </Sidebar> <div className="p-4 flex-grow"> <h1 className="text-2xl font-bold mb-4">Issues of {repoName}</h1> {buildStage !== 'Complete' ? ( <div className="flex flex-col items-center justify-center h-64"> <div className="loader"></div> <p className="mt-4">Status: {buildStage}</p> </div> ) : ( <div className="flex flex-col md:flex-row gap-4"> <div className="flex-grow bg-black shadow-md rounded-lg p-6"> <h2 className="text-xl font-semibold mb-4">Mega Linter Results</h2> <div className="overflow-x-auto"> <table className="min-w-full bg-black"> <thead className="bg-gray-100"> <tr> <th className="py-2 px-4 border-b text-left">S.No</th> <th className="py-2 px-4 border-b text-left">Issues</th> <th className="py-2 px-4 border-b text-left">Linter</th> <th className="py-2 px-4 border-b text-left">File</th> <th className="py-2 px-4 border-b text-left">Severity</th> </tr> </thead> <tbody> {filteredIssues.map((issue, index) => ( <tr key={index} className={index % 2 === 0 ? 'bg-gray-50' : 'bg-black'}> <td className="py-2 px-4 border-b">{index + 1}</td> <td className="py-2 px-4 border-b">{issue.message}</td> <td className="py-2 px-4 border-b">{issue.linter}</td> <td className="py-2 px-4 border-b">{issue.file}</td> <td className="py-2 px-4 border-b">{issue.severity}</td> </tr> ))} </tbody> </table> </div> </div> <div className="w-full md:w-64 bg-black shadow-md rounded-lg p-6"> <h2 className="text-xl font-semibold mb-4">Filters</h2> <div className="space-y-4"> <div> <label htmlFor="severity" className="block text-sm font-medium text-gray-700">Severity</label> <select id="severity" className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md" onChange={(e) => handleFilterChange('severity', e.target.value)} > <option value="">Select severity</option> {['Low', 'Medium', 'High'].map((option) => ( <option key={option} value={option}>{option}</option> ))} </select> </div> <div> <label htmlFor="language" className="block text-sm font-medium text-gray-700">Language</label> <select id="language" className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md" onChange={(e) => handleFilterChange('language', e.target.value)} > <option value="">Select language</option> {[...new Set(issues.map(issue => issue.language))].map((option) => ( <option key={option} value={option}>{option}</option> ))} </select> </div> <div> <label htmlFor="linter" className="block text-sm font-medium text-gray-700">Linter</label> <select id="linter" className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md" onChange={(e) => handleFilterChange('linter', e.target.value)} > <option value="">Select linter</option> {[...new Set(issues.map(issue => issue.linter))].map((option) => ( <option key={option} value={option}>{option}</option> ))} </select> </div> </div> </div> </div> )} </div> </div> ); }; export default Issues;
控制器函数代码
export const triggerPipeline = async (repo) => { try { if (!repo || !repo.name) { throw new Error('Invalid repository information'); } const { data: { user }, error } = await supabase.auth.getUser(); if (error) { throw new Error('Failed to fetch user data'); } const githubUsername = user.user_metadata.user_name; if (!githubUsername) { throw new Error('GitHub username not found in user metadata'); } const response = await fetch('http://localhost:8080/job/generate-issues/buildWithParameters', { method: 'POST', headers: { 'Authorization': 'Basic ' + btoa('username:password'), 'Content-Type': 'application/x-www-form-urlencoded' }, body: `REPO_URL=https://github.com/${githubUsername}/${repo.name}.git`, }); console.log('Response status:', response.status); console.log('Response headers:', JSON.stringify(Object.fromEntries(response.headers))); if (response.status === 201 || response.status === 303) { console.log('Jenkins pipeline triggered successfully'); return { message: 'Pipeline triggered successfully' }; } else { throw new Error(`Failed to trigger Jenkins pipeline: ${response.status}`); } } catch (error) { console.error('Error triggering Jenkins pipeline:', error); throw error; } }; export const checkBuildStatus = async () => { const buildUrl = 'http://localhost:8080/job/generate-issues/lastBuild/api/json'; const authHeader = 'Basic ' + btoa('username:password'); try { const response = await fetch(buildUrl, { headers: { 'Authorization': authHeader } }); if (!response.ok) { throw new Error(`Failed to fetch build status: ${response.status}`); } const buildData = await response.json(); console.log('Build data:', buildData); return { isBuilding: buildData.building, result: buildData.result, number: buildData.number }; } catch (error) { console.error('Error checking build status:', error); return { isBuilding: false, result: 'ERROR', number: null }; } }; export const getBuildResults = async (buildNumber) => { const logUrl = `http://localhost:8080/job/generate-issues/${buildNumber}/artifact/megalinter-reports/megalinter.log`; const authHeader = 'Basic ' + btoa('username:password'); try { const response = await fetch(logUrl, { headers: { 'Authorization': authHeader } }); if (!response.ok) { throw new Error(`Failed to fetch MegaLinter log: ${response.status}`); } const megaLinterLog = await response.text(); return { megaLinterLog }; } catch (error) { console.error('Error fetching MegaLinter log:', error); throw error; } };
问题分析与解决方法
1. 控制台触发成功后仍报错、动画状态显示失败
- 原因:
checkBuildStatus直接请求lastBuild,但Pipeline刚触发时新构建尚未生成,此时lastBuild指向旧构建,导致状态判断错误;且构建触发后立即轮询,新构建可能还未被Jenkins记录,返回的number为空,后续逻辑出错。 - 解决:
- 在
triggerPipeline中解析Jenkins响应头的Location字段,提取新构建的编号并返回:if (response.status === 201 || response.status === 303) { const locationHeader = response.headers.get('Location'); let buildNumber = null; if (locationHeader) { buildNumber = locationHeader.split('/').filter(part => !isNaN(part)).pop(); } console.log('Jenkins pipeline triggered successfully'); return { message: 'Pipeline triggered successfully', buildNumber }; } - 在
runPipeline中接收该编号,后续轮询和获取结果时直接使用此编号,避免依赖lastBuild:const { buildNumber } = await triggerPipeline({ name: repoName }); // 修改pollBuildStatus,请求指定编号的构建状态 const { isBuilding, result } = await checkBuildStatus(buildNumber); - 同步修改
checkBuildStatus函数,支持传入构建编号:export const checkBuildStatus = async (buildNumber) => { const buildUrl = buildNumber ? `http://localhost:8080/job/generate-issues/${buildNumber}/api/json` : 'http://localhost:8080/job/generate-issues/lastBuild/api/json'; // 其余逻辑保持不变 };
- 在
2. 构建完成后无法获取工件
- 原因:MegaLinter未生成指定路径的日志文件,或Jenkins归档路径配置有误,或请求账号无工件读取权限。
- 解决:
- 查看Jenkins构建日志,确认
megalinter-reports/megalinter.log是否生成; - 修改Pipeline归档步骤,明确指定日志文件路径:
archiveArtifacts artifacts: 'megalinter-reports/megalinter.log', allowEmptyArchive: true; - 检查Jenkins账号权限,确保用于请求的账号拥有读取该任务工件的权限;
- 在
getBuildResults中打印完整请求URL,确认路径
- 查看Jenkins构建日志,确认
相关产品推荐
相关产品推荐

