From 9ccb834bc31f00c645b8ccc45e074b88f59e0e48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=BF=83=E5=AE=87?= Date: Fri, 31 May 2024 16:37:01 +0800 Subject: [PATCH 1/8] sonar init --- package.json | 2 +- src/forge/Server/Index.jsx | 16 ++- src/forge/Server/api.js | 19 ++++ src/forge/Server/fetch.js | 10 ++ src/forge/Server/quality/detail.jsx | 163 +++++++++++++++++++++++++++ src/forge/Server/quality/detail.scss | 106 +++++++++++++++++ src/forge/javaFetch.js | 4 +- src/services/project.js | 2 +- 8 files changed, 315 insertions(+), 7 deletions(-) create mode 100644 src/forge/Server/api.js create mode 100644 src/forge/Server/fetch.js create mode 100644 src/forge/Server/quality/detail.jsx create mode 100644 src/forge/Server/quality/detail.scss diff --git a/package.json b/package.json index f0c3872de..7a1c3232d 100644 --- a/package.json +++ b/package.json @@ -200,7 +200,7 @@ "eslintConfig": { "extends": "react-app" }, - "proxy": "https://testforgeplus.trustie.net", + "proxy": "http://172.20.32.202:9999", "port": "3007", "devDependencies": { "@babel/core": "^7.23.6", diff --git a/src/forge/Server/Index.jsx b/src/forge/Server/Index.jsx index 9a6f57e4f..9a10189b8 100644 --- a/src/forge/Server/Index.jsx +++ b/src/forge/Server/Index.jsx @@ -23,15 +23,20 @@ const Pages = Loadable({ loader: () => import('./pages'), loading: Loading, }) +// 代码质量分析 +const Quality = Loadable({ + loader: () => import('./quality/detail'), + loading: Loading, +}) function ServerIndex(props){ const { projectDetail } = props; const { owner , projectsId } = props.match.params; useEffect(()=>{ // 非项目协作者不可进入服务tab (3397) - if(projectDetail && projectDetail.permission === ""){ - props.history.push(`/${owner}/${projectsId}`); - } + // if(projectDetail && projectDetail.permission === ""){ + // props.history.push(`/${owner}/${projectsId}`); + // } }, [projectDetail]) return( @@ -47,6 +52,11 @@ function ServerIndex(props){ () => () } > + () + } + > {/* 个人建站服务 */} {projectDetail && projectDetail.web_site && projectDetail.author.type === "User" && { + // 更新网页标题 + if(projectDetail){ + const { author, name} = projectDetail; + document.title = `reposyncer-${author.name}/${name}`; + } + getList().then(res => { + let issues = res.issues + let issuesHandled = {} + issues.forEach(e => { + if (issuesHandled[e.component]) { + issuesHandled[e.component].push(e) + } else { + issuesHandled[e.component] = [e] + } + }); + setIssues(issuesHandled) + }) + }, [projectDetail]) + + useEffect(() => { + if (selectedIssue) { + getCodeSnippets(selectedIssue.key).then(res => { + processIssues(res[selectedIssue.component].sources, issues[selectedIssue.component]) + setSnippetsDetail(res[selectedIssue.component]); + }) + } + },[selectedIssue]) + + useEffect(()=>{ + },[reload]) + + function addSpecialClassToRange(code, textRange, className) { + let textOnly = code.replace(/<[^>]+>/g, ''); // Remove HTML tags to get text only + let start = 0; + let end = textOnly.length; + + if (textRange.startLine === textRange.endLine) { + start = textRange.startOffset; + end = textRange.endOffset; + } + + let specialText = textOnly.slice(start, end); + let highlightedText = `${specialText}`; + + let currentIndex = 0; + let finalCode = ''; + let tag = false; + let tagBuffer = ''; + + for (let i = 0; i < code.length; i++) { + let char = code[i]; + + if (char === '<') { + tag = true; + tagBuffer += char; + } else if (char === '>') { + tagBuffer += char; + tag = false; + finalCode += tagBuffer; + tagBuffer = ''; + } else if (tag) { + tagBuffer += char; + } else { + if (currentIndex === start) { + finalCode += highlightedText; + } + if (currentIndex >= start && currentIndex < end) { + currentIndex++; + continue; + } + finalCode += char; + currentIndex++; + } + } + + return finalCode; + } + + function processIssues(data, issues) { + return data.map(item => { + // 处理 issue 的 textRange + issues.forEach(issue => { + if (item.line === issue.line) { + item.code = addSpecialClassToRange(item.code, issue.textRange, 'special'); + // 将 issue 添加到 data 项 + item.issue = issue + } + + // 处理 issue 的 flows 中的 textRange + issue.flows.forEach(flow => { + flow.locations.forEach(location => { + if (item.line === location.textRange.startLine) { + item.code = addSpecialClassToRange(item.code, location.textRange, 'special1'); + } + }); + }); + }); + return item; + }); + } + + return
+
+ { + issues && Object.entries(issues).map(([key, item]) =>
+
{ key.split(`:`)[1] }
+ { + item.map(e => ( +
setSelectedIssue(e)}> +

{ e.message }

+ { + e.flows && e.flows.length > 0 && e.flows.map(flow => { + 重复 + }) + } +
+ )) + } +
+ ) + } +
+ { + snippetsDetail &&
+
{ snippetsDetail.component.path }
+
+ + {snippetsDetail.sources.map((item, index) => ( + + { item.line } + +
+
+                    { issues[selectedIssue.component] && issues[selectedIssue.component].map(e => {
+                      if (e.line === item.line) {
+                        return 
setSelectedIssue(e)}>{ e.message }
+ } + })} +
+ + + ))} + +
+
+ } +
+} +export default issueDeail; \ No newline at end of file diff --git a/src/forge/Server/quality/detail.scss b/src/forge/Server/quality/detail.scss new file mode 100644 index 000000000..a14f5a466 --- /dev/null +++ b/src/forge/Server/quality/detail.scss @@ -0,0 +1,106 @@ +.bug-details { + display: flex; + max-height: 80vh; + .left-list { + width: 400px; + overflow-y: auto; + .doc-title { + width: 250px; + overflow: hidden; + white-space: nowrap; /* 防止文本换行 */ + overflow: hidden; /* 隐藏溢出的文本 */ + text-overflow: ellipsis; /* 显示省略号 */ + direction: rtl; /* 文字从右到左排列 */ + text-align: left; /* 文字从左边开始剪裁 */ + margin-top: 15px; + margin-bottom: 20px; + } + .issue { + padding: 15px 20px; + background: #FAFCFF; + border-radius: 3px 3px 3px 3px; + border: 1px solid rgba(42,97,255,0.23); + margin-bottom: 15px; + margin-right: 10px; + font-size: 14px; + word-break: break-word; + cursor: pointer; + &:hover { + border: 1px solid #466AFF; + } + } + .selected-issue { + background: #FFFFFF; + border: 1px solid #2A61FF; + } + } + .snippets { + width: 100%; + overflow-y: auto; + .head { + width: 100%; + height: 42px; + background: #FAFCFF; + border: 1px solid #ECEEEF; + font-weight: bold; + font-size: 16px; + color: #111010; + padding-left: 20px; + line-height: 42px; + } + .content { + display: flex; + border: 1px solid #ECEEEF; + tbody { + width: 100%; + } + tr:hover { + background: rgb(239, 242, 249); + } + .td-index { + width: 40px; + padding: 0 20px; + } + .td-code { + border-left: 1px solid rgb(221, 221, 221); + padding-left: 20px; + width: 100%; + .code { + position: relative; + white-space: pre-wrap; + overflow-wrap: anywhere; + tab-size: 4; + } + .issue-info { + padding: 15px 20px; + border-radius: 4px; + border: 1px solid rgb(197, 205, 223); + margin-right: 10px; + margin-bottom: 10px; + cursor: pointer; + word-break: break-word; + } + .selected-issue { + border: 1px solid rgb(253, 162, 155); + } + .special { + text-decoration: underline 2px wavy rgb(253, 162, 155); + text-decoration-skip-ink: none; + } + } + } + .cd { + font-family: "Ubuntu Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-style: italic; + color: rgb(95, 96, 102); + } + .k { + font-family: "Ubuntu Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-weight: 700; + color: rgb(162, 34, 160); + } + .s { + color: rgb(36, 117, 35); + } + } +} \ No newline at end of file diff --git a/src/forge/javaFetch.js b/src/forge/javaFetch.js index f33e69891..56ec563b7 100644 --- a/src/forge/javaFetch.js +++ b/src/forge/javaFetch.js @@ -6,9 +6,9 @@ import Login from './Wiki/components/Login'; export const TokenKey = 'autologin_trustie'; export default function javaFetch(actionUrl){ - if (window.location.href.indexOf('localhost') < 0) { + // if (window.location.href.indexOf('localhost') < 0) { axios.defaults.withCredentials = true; - } + // } // 创建axios实例 const service = axios.create({ diff --git a/src/services/project.js b/src/services/project.js index 4dc0eb859..0cbe82d56 100644 --- a/src/services/project.js +++ b/src/services/project.js @@ -39,7 +39,7 @@ export async function getProjectFunc (owner, projectsId) { export async function getProjectDetailFunc (owner, projectsId) { setHead() - const url = `${baseUrl}/${owner}/${projectsId}/detail.json`; + const url = `${baseUrl}/${owner}/${projectsId}/detail.json?debug=true`; return axiosInstance.get(url); } From aa509ef6135e7f6ea760d8460d57cf0840bfaac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=BF=83=E5=AE=87?= Date: Wed, 5 Jun 2024 18:20:16 +0800 Subject: [PATCH 2/8] cont. --- src/forge/Server/Index.jsx | 2 +- src/forge/Server/api.js | 22 ++- src/forge/Server/constants/quality.js | 16 ++ src/forge/Server/img/close.png | Bin 0 -> 310 bytes .../quality/component/ProblemDetail.jsx | 41 +++++ src/forge/Server/quality/component/index.scss | 44 +++++ src/forge/Server/quality/detail.jsx | 79 ++++++--- src/forge/Server/quality/detail.scss | 157 +++++++++++++++++- src/forge/Server/quality/index.jsx | 150 +++++++++++++++++ 9 files changed, 485 insertions(+), 26 deletions(-) create mode 100644 src/forge/Server/constants/quality.js create mode 100644 src/forge/Server/img/close.png create mode 100644 src/forge/Server/quality/component/ProblemDetail.jsx create mode 100644 src/forge/Server/quality/component/index.scss create mode 100644 src/forge/Server/quality/index.jsx diff --git a/src/forge/Server/Index.jsx b/src/forge/Server/Index.jsx index 9a10189b8..45f6c587a 100644 --- a/src/forge/Server/Index.jsx +++ b/src/forge/Server/Index.jsx @@ -25,7 +25,7 @@ const Pages = Loadable({ }) // 代码质量分析 const Quality = Loadable({ - loader: () => import('./quality/detail'), + loader: () => import('./quality/index'), loading: Loading, }) function ServerIndex(props){ diff --git a/src/forge/Server/api.js b/src/forge/Server/api.js index 32c4244d4..4a8bf7d80 100644 --- a/src/forge/Server/api.js +++ b/src/forge/Server/api.js @@ -1,9 +1,9 @@ import fetch from './fetch'; // 获取列表 -export function getList() { +export function getList(facets) { return fetch({ - url: `/api/issues/search?components=kingchanx-fluid-cloudnative_fluid&s=FILE_LINE&issueStatuses=CONFIRMED%2COPEN&ps=100&facets=cleanCodeAttributeCategories%2CimpactSoftwareQualities&additionalFields=_all&timeZone=Asia%2FShanghai`, + url: `/api/issues/search?components=kingchanx-fluid-cloudnative_fluid&s=FILE_LINE&issueStatuses=CONFIRMED%2COPEN&ps=100&facets=${ facets }&additionalFields=_all&timeZone=Asia%2FShanghai`, method: 'get', }); } @@ -12,7 +12,23 @@ export function getList() { // 代码片段 export function getCodeSnippets(key) { return fetch({ - url: `/api/sources/issue_snippets?issueKey=${key}`, + url: `/api/sources/issue_snippets?issueKey=${ key }`, + method: 'get', + }); +} + +// 问题分析 +export function getProblemDetail(key) { + return fetch({ + url: `/api/rules/show?key=${ key }`, + method: 'get', + }); +} + +// 数据汇总 +export function getProblemData(component, keys) { + return fetch({ + url: `/api/measures/component?component=${ component }&metricKeys=${ keys }`, method: 'get', }); } diff --git a/src/forge/Server/constants/quality.js b/src/forge/Server/constants/quality.js new file mode 100644 index 000000000..6921f9d55 --- /dev/null +++ b/src/forge/Server/constants/quality.js @@ -0,0 +1,16 @@ +export const issueType = { + BUG: 'BUG', + VULNERABILITY: '漏洞', + CODE_SMELL: '异味' +} + +export const issueTypeKeys = { + BUG: 'bugs', + VULNERABILITY: 'vulnerabilities', + CODE_SMELL: 'code_smells' +} +export const maintainability = { + LOW: '低', + MEDIUM: '中', + HIGH: '高' +} \ No newline at end of file diff --git a/src/forge/Server/img/close.png b/src/forge/Server/img/close.png new file mode 100644 index 0000000000000000000000000000000000000000..de4a9da8dcd87eb6bad881d76f3976c9f93bd63f GIT binary patch literal 310 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDB3?!H8JlO)I7>k44ofy`glX(f`hz9tCxB}__ z7{KFvE(=hCy(Gvlm_bslXa9jS{4=|*0EPB=x;TbdoIZMGBX5fV4{N{?mL3PkumlD! zwjE8Z5BB`$nEqhKpZ7nqGBzm59r|Z<_`@gDU(Gx3UGF_VeKXU}w5o%8lf&8tBA-OB zc2Q~WpXy>%A?M80z{#Y-GQl9@NTZvL!6imz+W;X3RlyyeEO|mrPEG-Kp4$w5#>Py^ zUE}WJ#jmNfwB1nQR*A!wm-B8+u}TX5w50dyM6)op8O(xb7&+P5Tbh|4HJv^p*4})2 oiQc(aP2SJ>$U+f=-bZ3|9|B@?ofc{|cboFyt=akR{0FJAF5dZ)H literal 0 HcmV?d00001 diff --git a/src/forge/Server/quality/component/ProblemDetail.jsx b/src/forge/Server/quality/component/ProblemDetail.jsx new file mode 100644 index 000000000..ca33e79f3 --- /dev/null +++ b/src/forge/Server/quality/component/ProblemDetail.jsx @@ -0,0 +1,41 @@ +import React, { useEffect, useState } from 'react'; +import { Drawer , Divider } from 'antd'; +import './index.scss' +import close from '../../img/close.png' +import moment from 'moment'; + +function ProblemDetail({visible, issue, issueDetail, onClose}){ + + onClose + + return( + issue && issueDetail && +
+ { issue.message } + +
+
+

+ { issue.message } + { issue.rule } +

+

+ { issueDetail.type } + { issueDetail.severity } + 生效时间 { moment(issue.creationDate).format('YYYY年MM月DD日') } + { issueDetail.lang } +

+ +
+
+
+ ) +} +export default ProblemDetail; diff --git a/src/forge/Server/quality/component/index.scss b/src/forge/Server/quality/component/index.scss new file mode 100644 index 000000000..6170527c7 --- /dev/null +++ b/src/forge/Server/quality/component/index.scss @@ -0,0 +1,44 @@ +.drawer-head { + height: 60px; + background: #252D41; + font-weight: 400; + font-size: 16px; + color: #FFFFFF; + padding: 20px 40px; + img { + width: 24px; + margin-left: auto; + float: right; + cursor: pointer; + } +} +.drawer-content { + height: 400px; + padding: 20px 40px; + overflow-y: auto; + .content-title { + font-weight: bold; + font-size: 18px; + color: #111010; + span { + float: right; + } + } + .content-info { + font-weight: 400; + font-size: 14px; + color: #5D6790; + margin-top: 15px; + span:nth-of-type(2) { + &::before { + content: "|"; + color: rgba(187,189,197,0.61); + margin-right: 13px; + margin-left: 13px; + } + } + span:nth-of-type(n + 3) { + margin-left: 70px; + } + } +} \ No newline at end of file diff --git a/src/forge/Server/quality/detail.jsx b/src/forge/Server/quality/detail.jsx index 195fcee82..252dd0dab 100644 --- a/src/forge/Server/quality/detail.jsx +++ b/src/forge/Server/quality/detail.jsx @@ -1,15 +1,21 @@ -import React, {useState, useEffect} from "react"; -import { getList, getCodeSnippets } from "../api"; +import React, {useState, useEffect, useRef } from "react"; +import { getList, getCodeSnippets, getProblemDetail } from "../api"; import './detail.scss'; +import ProblemDetail from "./component/ProblemDetail"; +import { issueType } from "../constants/quality" -function issueDeail(props){ +function issueDetail(props){ const { owner , projectsId } = props.match.params; const [ snippetsDetail, setSnippetsDetail ] = useState(null); const [ issues, setIssues ] = useState(null); const [ selectedIssue, setSelectedIssue ] = useState(null); + const [ selectedFlow, setSelectedFlow ] = useState(null); + const [ detailVisible, setDetailVisible ] = useState(true); + const [ issueInfo, setIssueInfo ] = useState(null); const [reload, setReload] = useState(); const { projectDetail } = props; + const codeRef = useRef(null); useEffect(()=>{ // 更新网页标题 @@ -99,21 +105,45 @@ function issueDeail(props){ // 将 issue 添加到 data 项 item.issue = issue } - - // 处理 issue 的 flows 中的 textRange - issue.flows.forEach(flow => { - flow.locations.forEach(location => { - if (item.line === location.textRange.startLine) { - item.code = addSpecialClassToRange(item.code, location.textRange, 'special1'); - } - }); + }); + // 处理选中 issue 的 flows 中的 textRange + selectedIssue.flows.forEach(flow => { + flow.locations.forEach(location => { + if (item.line === location.textRange.startLine) { + item.code = addSpecialClassToRange(item.code, location.textRange, 'special1'); + } }); }); return item; }); } + + // 点击右侧列表重复选中 + function selectRepeat(e, line) { + if (e.target.className === 'special1') { + setSelectedFlow(line) + } + } + + // 点击左侧列表跳转到对应位置 + function selectAndJumpRepeat(line) { + setSelectedFlow(line) + const element = document.getElementById(`line-${ line }`); + if (element) { + // 滚动到目标元素 + element.scrollIntoView({ behavior: 'smooth', block: "center" }); + } + } + + function detail() { + getProblemDetail(selectedIssue.rule).then(res => { + setIssueInfo(res?.rule) + setDetailVisible(true) + }) + } return
+ setDetailVisible(false)}>
{ issues && Object.entries(issues).map(([key, item]) =>
@@ -122,9 +152,13 @@ function issueDeail(props){ item.map(e => (
setSelectedIssue(e)}>

{ e.message }

+

{ issueType[e.type] }{ e.flows.length > 0 && +{e.flows.length} }

{ - e.flows && e.flows.length > 0 && e.flows.map(flow => { - 重复 + e.key === selectedIssue?.key && e.flows && e.flows.length > 0 && e.flows.map((flow, index) => { + return
selectAndJumpRepeat(flow.locations[0].textRange.startLine) }> + { index } + { flow.locations[0].msg } +
}) }
@@ -135,24 +169,29 @@ function issueDeail(props){ }
{ - snippetsDetail &&
+ snippetsDetail &&
{ snippetsDetail.component.path }
{snippetsDetail.sources.map((item, index) => ( - - { item.line } + <> + {item.line}
-
-                    { issues[selectedIssue.component] && issues[selectedIssue.component].map(e => {
+                    
 selectRepeat(e, item.line)}/>
+                    {issues[selectedIssue.component] && issues[selectedIssue.component].map(e => {
                       if (e.line === item.line) {
-                        return 
setSelectedIssue(e)}>{ e.message }
+ return
setSelectedIssue(e)}> + {e.message} + detail()}>问题分析 +
; } })}
+ { snippetsDetail.sources[index + 1] && (snippetsDetail.sources[index + 1].line - item.line > 1) && ......} + ))}
@@ -160,4 +199,4 @@ function issueDeail(props){ }
} -export default issueDeail; \ No newline at end of file +export default issueDetail; \ No newline at end of file diff --git a/src/forge/Server/quality/detail.scss b/src/forge/Server/quality/detail.scss index a14f5a466..480ee24f0 100644 --- a/src/forge/Server/quality/detail.scss +++ b/src/forge/Server/quality/detail.scss @@ -2,8 +2,9 @@ display: flex; max-height: 80vh; .left-list { - width: 400px; overflow-y: auto; + width: 350px; + flex-shrink: 0; .doc-title { width: 250px; overflow: hidden; @@ -24,14 +25,62 @@ margin-right: 10px; font-size: 14px; word-break: break-word; + font-family: PingFang SC, PingFang SC; + font-size: 14px; + color: #111010; cursor: pointer; &:hover { border: 1px solid #466AFF; } + .issue-type { + font-weight: 400; + color: #5D6790; + } + .issue-duplication { + margin-left: 5px; + padding: 0 8px; + background: #57575B; + border-radius: 4px 4px 4px 4px; + font-weight: 400; + color: #FFFFFF; + } + .repeat-item { + span { + border-radius: 2px 2px 2px 2px; + color: #FFFFFF; + } + span:nth-of-type(1) { + display: inline-block; + text-align: center; + line-height: 22px; + width: 22px; + height: 22px; + background: #C7797E; + } + span:nth-of-type(2) { + display: inline-block; + line-height: 22px; + padding: 0 7px; + margin-left: 9px; + height: 22px; + background: #A8A8A8; + } + } + .selected-repeat { + span:nth-of-type(1) { + background: #D4333F; + } + span:nth-of-type(2) { + background: #475760; + } + } } .selected-issue { background: #FFFFFF; border: 1px solid #2A61FF; + .issue-duplication { + background: #D4333F; + } } } .snippets { @@ -87,6 +136,20 @@ text-decoration: underline 2px wavy rgb(253, 162, 155); text-decoration-skip-ink: none; } + .special1 { + background-color: rgba(253, 162, 155, 0.15); + cursor: pointer; + } + .selected-flow { + .special1 { + background-color: rgba(253, 162, 155, 0.5); + } + } + } + .td-ellipsis { + text-align: center; + border-top: 1px solid #ECEEEF; + border-bottom: 1px solid #ECEEEF; } } .cd { @@ -103,4 +166,94 @@ color: rgb(36, 117, 35); } } -} \ No newline at end of file +} +.issue-total { + .container { + background: #FFFFFF; + box-shadow: 0px 0px 6px 1px rgba(7,70,165,0.08); + border-radius: 4px 4px 4px 4px; + border: 2px solid #FFFFFF; + } + .issue-list { + display: flex; + .left-content { + width: 218px; + .filter-title { + font-weight: bold; + font-size: 17px; + color: #000000; + padding: 20px; + } + .filter-item { + padding: 0 20px; + display: flex; + justify-content: space-between; + align-items: center; + height: 54px; + cursor: pointer; + &:hover { + background: #466bff0c; + } + } + .ant-divider { + margin: 20px; + width: auto; + min-width: unset; + } + } + .right-content { + margin-left: 17px; + flex-grow: 1; + padding: 20px; + padding-top: 10px; + .doc-title { + font-weight: bold; + font-size: 16px; + color: #111010; + margin: 19px 0; + } + .issue { + padding: 15px 20px; + background: #FAFCFF; + border-radius: 3px 3px 3px 3px; + border: 1px solid rgba(42,97,255,0.23); + margin-bottom: 12px; + .content-info { + font-weight: 400; + font-size: 14px; + color: #5D6790; + margin-top: 10px; + > span:nth-child(2), > span:nth-child(3) { + &::before { + content: "|"; + color: rgba(187,189,197,0.61); + margin-right: 13px; + margin-left: 13px; + } + } + span:nth-of-type(n + 4) { + float: right; + } + } + .issue-message { + font-weight: bold; + font-size: 15px; + color: #111010; + span:nth-of-type(1) { + font-weight: 400; + font-size: 14px; + color: #466AFF; + margin-left: 16px; + cursor: pointer; + } + span:nth-of-type(2) { + float: right; + } + } + } + } + } + .total-info { + padding: 20px; + } +} \ No newline at end of file diff --git a/src/forge/Server/quality/index.jsx b/src/forge/Server/quality/index.jsx new file mode 100644 index 000000000..16b561f8b --- /dev/null +++ b/src/forge/Server/quality/index.jsx @@ -0,0 +1,150 @@ +import React, {useState, useEffect, useRef } from "react"; +import { getList, getCodeSnippets, getProblemDetail, getProblemData } from "../api"; +import './detail.scss'; +import ProblemDetail from "./component/ProblemDetail"; +import { issueType, issueTypeKeys, maintainability } from "../constants/quality" +import { Divider } from 'antd'; + +function issueList(props){ + const [ issues, setIssues ] = useState({}); + const [ baseInfo, setBaseInfo ] = useState({}); + const [ baseData, setBaseData ] = useState({}); + const [ listFilter, setListFilter ] = useState({}); + const [ selectedIssue, setSelectedIssue ] = useState(null); + const [ detailVisible, setDetailVisible ] = useState(true); + const [ issueInfo, setIssueInfo ] = useState(null); + const { projectDetail } = props; + const metricKeys = 'lines,coverage,lines_to_cover,duplicated_lines_density,maintainability_issues,vulnerabilities,bugs,code_smells' + const facets = 'impactSeverities,tags,types' + useEffect(() => { + let newContainer = document.getElementsByClassName("newContainer")[0] + newContainer.style.backgroundColor = '#F7F9FCFF' + return () => { + newContainer.style.backgroundColor = '#FFFFFF' + } + },[]) + + useEffect(()=>{ + // 更新网页标题 + if(projectDetail){ + const { author, name} = projectDetail; + document.title = `reposyncer-${author.name}/${name}`; + } + getProblemData('kingchanx-fluid-cloudnative_fluid', metricKeys).then(res => { + const measures = res.component.measures; + const result = {}; + measures.forEach(measure => { + result[measure.metric] = measure.value; + }); + setBaseData(result) + }) + getList(facets).then(res => { + let issues = res.issues + let issuesHandled = {} + issues.forEach(e => { + if (issuesHandled[e.component]) { + issuesHandled[e.component].push(e) + } else { + issuesHandled[e.component] = [e] + } + }); + const filter = {} + res.facets.forEach(e => { + filter[e.property] = e.values + }) + console.log(filter); + setListFilter(filter) + setBaseInfo(res) + setIssues(issuesHandled) + }) + }, [projectDetail]) + + function detail(e) { + setSelectedIssue(e) + getProblemDetail(e.rule).then(res => { + setIssueInfo(res?.rule) + setDetailVisible(true) + }) + } + + return
+ setDetailVisible(false)}> +
+

代码分析结果

+
+ +
+
+ {issues &&

发现{ baseInfo.total }个问题

} +
+
+
+
+ 问题类型 +
+ { + listFilter.types && listFilter.types.map(e => ( +
+ { issueType[e.val] } + { e.count } +
)) + } + +
+
+
+ 严重程度 +
+ { + listFilter.impactSeverities && listFilter.impactSeverities.map(e => ( +
+ { maintainability[e.val] } + { e.count } +
)) + } + +
+
+
+ 标签 +
+ { + listFilter.tags && listFilter.tags.map(e => ( +
+ { e.val } + { e.count } +
)) + } +
+
+
+ { + issues && Object.entries(issues).map(([key, item]) =>
+
{ key.split(`:`)[1] }
+ { + item.map(e => ( +
setSelectedIssue(e)}> +

{ e.message } detail(e)}>问题分析L{ e.line }

+

+ { issueType[e.type] } + { maintainability[e.impacts[0].severity] } + { e.debt }工作 + + { + e.tags && e.tags.map((tag, index) => ( + { tag }{ index < e.tags.length - 1 ? ',' : '' } + )) + } + +

+
+ )) + } +
+ ) + } +
+
+
+} +export default issueList; \ No newline at end of file From 5c150943448f9a1c9ac85e6ec7e1ebd28f9982f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=BF=83=E5=AE=87?= Date: Thu, 6 Jun 2024 15:59:56 +0800 Subject: [PATCH 3/8] update --- src/forge/Server/api.js | 5 +- src/forge/Server/quality/detail.scss | 47 ++++++++++++++- src/forge/Server/quality/index.jsx | 90 ++++++++++++++++++++++++---- src/forge/Utils/utils.js | 12 ++++ 4 files changed, 141 insertions(+), 13 deletions(-) create mode 100644 src/forge/Utils/utils.js diff --git a/src/forge/Server/api.js b/src/forge/Server/api.js index 4a8bf7d80..0047ee2e8 100644 --- a/src/forge/Server/api.js +++ b/src/forge/Server/api.js @@ -1,10 +1,11 @@ import fetch from './fetch'; // 获取列表 -export function getList(facets) { +export function getList(params) { return fetch({ - url: `/api/issues/search?components=kingchanx-fluid-cloudnative_fluid&s=FILE_LINE&issueStatuses=CONFIRMED%2COPEN&ps=100&facets=${ facets }&additionalFields=_all&timeZone=Asia%2FShanghai`, + url: `/api/issues/search?components=kingchanx-fluid-cloudnative_fluid&s=FILE_LINE&issueStatuses=CONFIRMED%2COPEN&ps=100&&additionalFields=_all&timeZone=Asia%2FShanghai`, method: 'get', + params }); } diff --git a/src/forge/Server/quality/detail.scss b/src/forge/Server/quality/detail.scss index 480ee24f0..e01990acc 100644 --- a/src/forge/Server/quality/detail.scss +++ b/src/forge/Server/quality/detail.scss @@ -178,6 +178,7 @@ display: flex; .left-content { width: 218px; + flex-shrink: 0; .filter-title { font-weight: bold; font-size: 17px; @@ -186,15 +187,19 @@ } .filter-item { padding: 0 20px; + margin: 2px 0; display: flex; justify-content: space-between; align-items: center; - height: 54px; + height: 50px; cursor: pointer; &:hover { background: #466bff0c; } } + .selected-item { + background: #466bff17; + } .ant-divider { margin: 20px; width: auto; @@ -239,6 +244,7 @@ font-weight: bold; font-size: 15px; color: #111010; + word-break: break-word; span:nth-of-type(1) { font-weight: 400; font-size: 14px; @@ -255,5 +261,44 @@ } .total-info { padding: 20px; + .info-list { + display: flex; + .total-item { + display: flex; + flex-direction: column; + min-height: 53px; + font-weight: 400; + font-size: 15px; + color: #525151; + > span:nth-child(2) { + font-weight: bold; + font-size: 18px; + color: #111010; + } + .item-more { + font-weight: 400; + font-size: 12px; + color: #525151; + span { + color: #466AFF; + } + } + } + .total-item:not(:last-child) { + margin-right: 40px; + } + .total-item:nth-child(n + 2) { + margin-left: 64px; + position: relative; + &::after { + position: absolute; + left: -40px; + content: ''; + width: 0px; + height: 53px; + border-left: 1px solid rgba(187,189,197,0.61); + } + } + } } } \ No newline at end of file diff --git a/src/forge/Server/quality/index.jsx b/src/forge/Server/quality/index.jsx index 16b561f8b..95d6696b5 100644 --- a/src/forge/Server/quality/index.jsx +++ b/src/forge/Server/quality/index.jsx @@ -4,6 +4,7 @@ import './detail.scss'; import ProblemDetail from "./component/ProblemDetail"; import { issueType, issueTypeKeys, maintainability } from "../constants/quality" import { Divider } from 'antd'; +import { formatNumber } from '../../Utils/utils' function issueList(props){ const [ issues, setIssues ] = useState({}); @@ -12,7 +13,10 @@ function issueList(props){ const [ listFilter, setListFilter ] = useState({}); const [ selectedIssue, setSelectedIssue ] = useState(null); const [ detailVisible, setDetailVisible ] = useState(true); - const [ issueInfo, setIssueInfo ] = useState(null); + const [ issueInfo, setIssueInfo ] = useState({}); + const [ selectedType, setSelectedType ] = useState([]); + const [ selectedSeverity, setSelectedSeverity ] = useState([]); + const [ selectedTag, setSelectedTag ] = useState([]); const { projectDetail } = props; const metricKeys = 'lines,coverage,lines_to_cover,duplicated_lines_density,maintainability_issues,vulnerabilities,bugs,code_smells' const facets = 'impactSeverities,tags,types' @@ -38,7 +42,17 @@ function issueList(props){ }); setBaseData(result) }) - getList(facets).then(res => { + getIssueList() + }, [projectDetail]) + + function getIssueList() { + let params = { + facets: facets, + types: selectedType.join(','), + impactSeverities: selectedSeverity.join(','), + tags: selectedTag.join(','), + } + getList(params).then(res => { let issues = res.issues let issuesHandled = {} issues.forEach(e => { @@ -52,12 +66,15 @@ function issueList(props){ res.facets.forEach(e => { filter[e.property] = e.values }) - console.log(filter); setListFilter(filter) setBaseInfo(res) setIssues(issuesHandled) }) - }, [projectDetail]) + } + + useEffect(() => { + getIssueList() + },[selectedType, selectedSeverity, selectedTag]) function detail(e) { setSelectedIssue(e) @@ -66,13 +83,66 @@ function issueList(props){ setDetailVisible(true) }) } + + function selectType(e) { + let index = selectedType.indexOf(e) + if (index > -1) { + selectedType.splice(index, 1) + setSelectedType([...selectedType]) + } else { + setSelectedType([...selectedType, e]) + } + } + + function selectSeverity(e) { + let index = selectedSeverity.indexOf(e) + if (index > -1) { + selectedSeverity.splice(index, 1) + setSelectedSeverity([...selectedSeverity]) + } else { + setSelectedSeverity([...selectedSeverity, e]) + } + } + + function selectTags(e) { + let index = selectedTag.indexOf(e) + if (index > -1) { + selectedTag.splice(index, 1) + setSelectedTag([...selectedTag]) + } else { + setSelectedTag([...selectedTag, e]) + } + } return
setDetailVisible(false)}>

代码分析结果

-
- +
+
+ Bugs + { baseData.bugs } +
+
+ 漏洞 + { formatNumber(baseData.vulnerabilities) } +
+
+ 异味 + { formatNumber(baseData.code_smells) } +
+
+ 重复率 + { baseData.duplicated_lines_density }% 代码{ formatNumber(baseData.lines) } +
+
+ 覆盖率 + { baseData.coverage }% 代码{ formatNumber(baseData.lines_to_cover) } +
+
+ 代码行数 + { baseData.lines && baseData.lines.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") } +
{issues &&

发现{ baseInfo.total }个问题

} @@ -80,11 +150,11 @@ function issueList(props){
- 问题类型 + 问题类型 {}
{ listFilter.types && listFilter.types.map(e => ( -
+
selectType(e.val)}> { issueType[e.val] } { e.count }
)) @@ -97,7 +167,7 @@ function issueList(props){
{ listFilter.impactSeverities && listFilter.impactSeverities.map(e => ( -
+
selectSeverity(e.val)}> { maintainability[e.val] } { e.count }
)) @@ -110,7 +180,7 @@ function issueList(props){
{ listFilter.tags && listFilter.tags.map(e => ( -
+
selectTags(e.val)}> { e.val } { e.count }
)) diff --git a/src/forge/Utils/utils.js b/src/forge/Utils/utils.js new file mode 100644 index 000000000..6aa8c57f7 --- /dev/null +++ b/src/forge/Utils/utils.js @@ -0,0 +1,12 @@ +export function formatNumber(num) { + if (num === undefined || num === null) return + if (num >= 1e9) { + return (num / 1e9).toFixed(1) + 'B'; // 十亿 + } else if (num >= 1e6) { + return (num / 1e6).toFixed(1) + 'M'; // 百万 + } else if (num >= 1e3) { + return (num / 1e3).toFixed(1) + 'K'; // 千 + } else { + return num.toString(); // 小于 1000 的数字原样返回 + } +} \ No newline at end of file From 5fa8984a2c5807465faa04ba24638d3f296b0d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=BF=83=E5=AE=87?= Date: Fri, 7 Jun 2024 15:48:30 +0800 Subject: [PATCH 4/8] cont. --- src/forge/Server/Index.jsx | 13 +- src/forge/Server/List.jsx | 24 +- src/forge/Server/api.js | 5 +- src/forge/Server/constants/quality.js | 19 + src/forge/Server/img/logo3-1.png | Bin 0 -> 3533 bytes src/forge/Server/img/logo3.png | Bin 3533 -> 1853 bytes src/forge/Server/img/logo4-1.png | Bin 0 -> 2182 bytes src/forge/Server/img/logo4.png | Bin 0 -> 2886 bytes src/forge/Server/img/quality/back.png | Bin 0 -> 654 bytes src/forge/Server/img/quality/bugs.png | Bin 0 -> 397 bytes src/forge/Server/img/quality/code_lines.png | Bin 0 -> 370 bytes src/forge/Server/img/quality/code_smells.png | Bin 0 -> 468 bytes src/forge/Server/img/quality/cover.png | Bin 0 -> 641 bytes src/forge/Server/img/quality/folder.png | Bin 0 -> 390 bytes src/forge/Server/img/quality/high.png | Bin 0 -> 417 bytes src/forge/Server/img/quality/label.png | Bin 0 -> 270 bytes src/forge/Server/img/quality/low.png | Bin 0 -> 363 bytes src/forge/Server/img/quality/medium.png | Bin 0 -> 339 bytes src/forge/Server/img/quality/repeat.png | Bin 0 -> 439 bytes .../Server/img/quality/vulnerabilities.png | Bin 0 -> 337 bytes src/forge/Server/quality/detail.jsx | 71 +++- src/forge/Server/quality/detail.scss | 185 ++++++-- src/forge/Server/quality/index.jsx | 398 ++++++++++-------- .../Server/reposyncer/component/storeList.jsx | 2 +- src/forge/Utils/utils.js | 4 + 25 files changed, 468 insertions(+), 253 deletions(-) create mode 100644 src/forge/Server/img/logo3-1.png create mode 100644 src/forge/Server/img/logo4-1.png create mode 100644 src/forge/Server/img/logo4.png create mode 100644 src/forge/Server/img/quality/back.png create mode 100644 src/forge/Server/img/quality/bugs.png create mode 100644 src/forge/Server/img/quality/code_lines.png create mode 100644 src/forge/Server/img/quality/code_smells.png create mode 100644 src/forge/Server/img/quality/cover.png create mode 100644 src/forge/Server/img/quality/folder.png create mode 100644 src/forge/Server/img/quality/high.png create mode 100644 src/forge/Server/img/quality/label.png create mode 100644 src/forge/Server/img/quality/low.png create mode 100644 src/forge/Server/img/quality/medium.png create mode 100644 src/forge/Server/img/quality/repeat.png create mode 100644 src/forge/Server/img/quality/vulnerabilities.png diff --git a/src/forge/Server/Index.jsx b/src/forge/Server/Index.jsx index 45f6c587a..6712c499e 100644 --- a/src/forge/Server/Index.jsx +++ b/src/forge/Server/Index.jsx @@ -28,6 +28,12 @@ const Quality = Loadable({ loader: () => import('./quality/index'), loading: Loading, }) +// 代码质量分析详情 +const QualityDetail = Loadable({ + loader: () => import('./quality/detail'), + loading: Loading, +}) + function ServerIndex(props){ const { projectDetail } = props; const { owner , projectsId } = props.match.params; @@ -52,7 +58,12 @@ function ServerIndex(props){ () => () } > - () + } + > + () } diff --git a/src/forge/Server/List.jsx b/src/forge/Server/List.jsx index 280c07f56..23e6ddf54 100644 --- a/src/forge/Server/List.jsx +++ b/src/forge/Server/List.jsx @@ -17,13 +17,13 @@ function Main(props){ } }, [projectDetail]) - useEffect(()=>{ - if(current_user && !current_user.login){ - props.history.push(`/login?go_page=/${owner}/${projectsId}/service`); - }else{ - setHas_trace_user(current_user.has_trace_user); - } - },[current_user]) + // useEffect(()=>{ + // if(current_user && !current_user.login){ + // props.history.push(`/login?go_page=/${owner}/${projectsId}/service`); + // }else{ + // setHas_trace_user(current_user.has_trace_user); + // } + // },[current_user]) function onOk(){ setVisible(false); @@ -64,6 +64,16 @@ function Main(props){ 查看详情 +
  • +
    + + 代码质量分析工具 +
    +

    提供自动化的静态代码检查能力,能够识别代码中的缺陷、安全漏洞、代码异味,同时产出代码覆盖率和重复度的详细报告

    + + 查看详情 + +
  • {/* 是站点仓库则显示,否则隐藏 */} {projectDetail && projectDetail.web_site && projectDetail.author.type === "User" &&
  • diff --git a/src/forge/Server/api.js b/src/forge/Server/api.js index 0047ee2e8..d5daf53de 100644 --- a/src/forge/Server/api.js +++ b/src/forge/Server/api.js @@ -1,9 +1,10 @@ import fetch from './fetch'; // 获取列表 -export function getList(params) { +export function getList(params, owner, repo) { return fetch({ - url: `/api/issues/search?components=kingchanx-fluid-cloudnative_fluid&s=FILE_LINE&issueStatuses=CONFIRMED%2COPEN&ps=100&&additionalFields=_all&timeZone=Asia%2FShanghai`, + // url: `/api/v1/${owner}/${repo}/sonarqubes/issues_search.json`, + url: `/api/issues/search?components=kingchanx-fluid-cloudnative_fluid&s=FILE_LINE&issueStatuses=CONFIRMED%2COPEN&&additionalFields=_all&timeZone=Asia%2FShanghai`, method: 'get', params }); diff --git a/src/forge/Server/constants/quality.js b/src/forge/Server/constants/quality.js index 6921f9d55..2cf44037a 100644 --- a/src/forge/Server/constants/quality.js +++ b/src/forge/Server/constants/quality.js @@ -1,3 +1,10 @@ +import bugs from '../img/quality/bugs.png' +import vulnerabilities from '../img/quality/vulnerabilities.png' +import code_smells from '../img/quality/code_smells.png' +import low from '../img/quality/low.png' +import medium from '../img/quality/medium.png' +import high from '../img/quality/high.png' + export const issueType = { BUG: 'BUG', VULNERABILITY: '漏洞', @@ -13,4 +20,16 @@ export const maintainability = { LOW: '低', MEDIUM: '中', HIGH: '高' +} + +export const issueTypeIcon = { + BUG: bugs, + VULNERABILITY: vulnerabilities, + CODE_SMELL: code_smells +} + +export const maintainabilityIcon = { + LOW: low, + MEDIUM: medium, + HIGH: high } \ No newline at end of file diff --git a/src/forge/Server/img/logo3-1.png b/src/forge/Server/img/logo3-1.png new file mode 100644 index 0000000000000000000000000000000000000000..c2a6c85f09c65aceb4317b4f88df8dfa60398bc9 GIT binary patch literal 3533 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 Hu0mjf0L;6? literal 0 HcmV?d00001 diff --git a/src/forge/Server/img/logo3.png b/src/forge/Server/img/logo3.png index c2a6c85f09c65aceb4317b4f88df8dfa60398bc9..66c617f97cecdf7d9354102b829848edb72a8f5c 100644 GIT binary patch literal 1853 zcmV-D2g3M?P)Px*_en%SRCwC$ntM|dXB5W$2z&%S0^gwgQLQ4E1ws-+5|)r~2_Y;26eHlvO$rDJ zL}frApji`udEvR*@ZKvqiR;}ZxZKsZ-W9YR1pT0{%%!TMCyqn8r&&)H=K6`ji z_WaC*%eZf!SYnAKmRMqmC6-uXB|&9fr!sP`=YMtxD;7?zQ)z;QdH9-SJ$p?8*kRju z{S_LuS#K8zZ&K~fqu@a?1kWf=B_AE_KF;GR*L;34{$#F z!UK9)iE^)$K+fe7h=&Ug=&~B+UQ)rfmRVOrEVJ-{x@eSlk%nAXDa6AC2b5aUpuDd& z5HAT2=%N-;OFGEApoMt20D)E^YM~OSMJ(+3UiWD+&`I}&2h^!Y`RDbJKVJ>3Q(>I1 zzTkisI1MP@X#ngH52x%*8s#*cz}M@sQo)?TglDd|JkY5|B>AcV3a1(%F5baO(7nDjQ$McDk@$Te-4h$g0-~cEF2jPPc2IY`GhH43gIymUycs?=u zcz5zZ`z@$szzT{}R*2LU0~VU(yjZ((z(O9VvfqYE`i7yT-v%BZ4&%G`6@9~@Q2T6F zuIJ;~SN0C^0JQfE!h(`7Peat+V+;6WEt~f=FLurY(4G;51v^stU31JEADbD)yjW%) zfOd}})yXkXb&mq;O0RRw4`^f$_+NCY?lF1~^PvjowHZpeh&P)zJ0pJ9bC_|yORgnK7mR*?4WL+1hv;WWEN(? zk4(Tb=l9%io!cE|%iLxZ^LYSjcA~Q5^FW*DftxS*KC{Zq z^H%QXh&YDn3WVA0OfNfrelwbRnieN{pc?ZcDm%6SAG8Rc$Zq=BqD6CT!Nu|GN##(O z_#c;jo@nQ5%;$Lk+Ps7`N0*@N=mkh*r<)f8zY#VqWZ?VmJ#uCG?@a&aFK@U?MLVC}ojg!x1!en(jBAios=H!`AN11hREAQ@e!U(X2K zhx_>|{Vno9tE%px%F5eNS$PMN({H;gDsGkHJu>mNJ(Bb6?&N`1+(lSWsrwE*eiu?G z^8i$P4^@@l1MI+hwf1`m%=bXBeY`t)pql%rTJr-`m)(cmyeiCnA@uclcj7?x^pB{T z{t5J0spjnN zJc5*Sw%eI zA2HO6G=uC3)XJa2wqwOI8|6>=0UFP$EqVsEMZbg3rO&+ALHBv+8>P<#2(<7y7$wgk zPA)v4#)22Xs;=NA>|F0Ap8CQAT1UNtI_eKF=6T$gsi4e5pP^m}4=9uS7wo9c?>ZKl z$^BD+K(qe_Cg(M>ucTj)O>#@vP zZ-occbnyL4(--d{E;{&*797xq18%C}z}A*&{}wbH_y=O>8~cP>Or|@n@$-K(W2L73 r|7GkGODwU(5=$(x#1c!aB&h!Zw=~S-z&ab#00000NkvXXu0mjf=5U@9 literal 3533 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 Hu0mjf0L;6? diff --git a/src/forge/Server/img/logo4-1.png b/src/forge/Server/img/logo4-1.png new file mode 100644 index 0000000000000000000000000000000000000000..37e43bacc604f496117f93bff753367832b60516 GIT binary patch literal 2182 zcmaKtc{Cf?7sq4Yi4;XL1fjK+u{EtNK@dyrRQj`qsxU~k7_C~1Xpv-QGOccwlo)Co zZHHD99i=)gu`i|8(PC_^kTmFMbB@#V`}y^q^X|Lnd%yQ{?)&FGg8K;vDRE_S001C` zcC_={J##;AQGwkk5#|-Vd+^13I@kbex*+qr`KY^#H)>b>_Cmyc`nG{rbv(1b4TalZ z=XIeXKUeL(>a~mdtk{3id7iH$Pyc^Ro^H2aul<)XcySN7FO3J;cL1LFUywZ-FX7#Y z7r!oj1G{J2cb>f}kMO_bJ;T?@?vTFEBL3NxZl3cz;RWCm5EGY>l#-SK0%bvR@(KqP zmB0{HC{zumqpN3NXlP`76n^Zuxuumg0)a%ixw*T0`}kx26A%;>92^=JaV9c4Ha0E} ze?B4UQfg{iT6#_{p^!)qr9i6Yby1SXo zp5DHp*JI;vCMG5)r)K8fEiJFCtgf!TUuSJ>vN<2Ow&lC~+W`Qf2DBa0J85+32Islu!L5 zATQ<8Is~^WRFXWjjrHNOq|vuSRdH+1B{;6^r@%<7#qX+~rN)9PS@k}P5_uyAV0&GZ z9O~hf=J4_6qDAG!oX4z$?a2F_o1)LPt9qRS3kKf_p8zctW3~u zTJd&*0VAXu8!o>%H8O=7Yd)Mw`6wU@Y;1_(gG&nN{)0i~Pd%&VBUyxx!?pP{FppHG z8HiG0Y4TAhImMTg*orhP$n(M_20%hsYM>vXiVi`WM`WY;%x$ODNx~WNlgnc*(NK^- z6qr-sG-P5%fB|!D|0|WzDStr2Rtxx2Q%l%q3(vg9BweBVdg>Z)!v;x5M!BAbsA}uBx!!bQD zXagjwvqP($jpv-ToU3F%LG{`40dF4pEtG4MvH&5Eb=Or|I`E=oW*;cH&KKJ zRf7b7xbE6+yfNF>pAJjP9#)EBN9A+mMxVN#hq@jC%OYpWrKuee z^nuK{ku0Lm1N(Ia?(2q_i#Z9d;bVR=`cDh0 zb$-`a#L^%3arC2H_0-i6AF;^Ntta+|7#M=mfnV<&7{yAW2~ooUA{$P;T6G|47+Zd= zJD%Qm3R~dNKJF}I;#*aUfBZIzAV;GOoyqtKt4cRZ)XA$*)jilIV`g>0vA+dQliUb_SkCw5DwAUhA_Fz+KP2TKtRiku%VtiK50<$1IJt3iKLa`w^HRB wx%46@N$gI7&5g(EBb-JB9dFu~7d3p|`tD`Aa)6(B`R9)ZjXGghV-uY5FXNa^;s5{u literal 0 HcmV?d00001 diff --git a/src/forge/Server/img/logo4.png b/src/forge/Server/img/logo4.png new file mode 100644 index 0000000000000000000000000000000000000000..cf9d0317f3b3495c33e7120bf23d6e1512d1ceae GIT binary patch literal 2886 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 SENunGIfJLGpUXO@geCx$vMcrg literal 0 HcmV?d00001 diff --git a/src/forge/Server/img/quality/bugs.png b/src/forge/Server/img/quality/bugs.png new file mode 100644 index 0000000000000000000000000000000000000000..a921c64b514817acbd087f4296c22b7842164fb7 GIT binary patch literal 397 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k3?#4J%UA`Z7>k44ofy`glX(f`xCZ!yxB_Vd zBw!2%Mi>Ah4kUpx5E7yUBCfr`=o`==v63LaU_2e+!h`o8 zzHLxX1**8>>Eak-ar)?$n_^80BCG+N9;zOad;b4lX{dTg^!3@#O%JAdiXAS;P zl7G>`<7cNIbL`sVAhs_t_Ha4#`^kx7Qe1%oN4|B+U*OVP5jdqKZE7x8^O9E;i~GKQ zblIw=C3E$gZDR68mK*z=&pcVawf|7md-sx$7bSLH_;<=9gj-)D?Q};={ZfNX3-?+X zf0^IOFVXpLrB22X4<(mh>E-TR(u<;(6#D<`&24kml5tkz-V~!~x>R$+cd?10Ds|y2 u*&9xl#s_Tr9+x}SFnxLpOWpCgUm3IImEZBzi&z5v$KdJe=d#Wzp$Pz2-;=%o literal 0 HcmV?d00001 diff --git a/src/forge/Server/img/quality/code_lines.png b/src/forge/Server/img/quality/code_lines.png new file mode 100644 index 0000000000000000000000000000000000000000..7ffec82cf173ae51b7efe2374756d685fe6e810b GIT binary patch literal 370 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k3?#4J%UA`Z7>k44ofy`glX(f`I0g8GxB_Vd z7%((61Tu|{jg7DYpg5WoL?rk7UOk{Gq9s9o!3+Wl2KEjC2?g~H{S)Rd*l^&&gZCeP ziA~}MD%k7k;uvCa`s|h4LQM)Htq)n3%n0oIu>b$+vc}}JOD4g-YrUT(-^k&+bpM!4 zS@JctfRjJdtX91gGr4y02kV!eKax%Vh3~$-;3cbw$ovHvHV%t9dQb1xkm7Zi#%LwF z)$!t#+XgFGGrD)VPo6km-<`q3?;RuS%%IlUujef(?4A_NJRz0wgx|l7%R=1RH)YLx zo>M-3PSuB9Q|xwZxxM_#yaQQGAAGAW72e-hd|+Xp9`~l5X`5pAeyY3UoB#h?@-8NY XuZE%X?3LYT&2U}R(jq=6(FfXL!f0};Ow!TlR(v`k5mUoeA!f`Pq*e?WXfenEXh|AhGq z)^FH<;QWRA58i)pTD|iN&{@o$E{-7*l6wzcESqg0;(D-n>4_;pUK|NMTmS#J*4EpU zto}7RJpD`k&6l-y@y~yzU$}oG|J}1C`M0$D?R?I=?XzIFmC_JfWy&mmdAZmLPj0>0 zx(6c5%UW}S4s4yIw&3zv1{YD=qpJ>??U=>K_T^S2+oUqi7bO`D3fB)UIm|YhPr1jc zr=YV)cfnz%y>}c6Cwear<5|F6jlT4}oEs!m=33^oQ&S3j3^P6|k3?#4J%UA`Z7>k44ofy`glX(f`EDG=maRt&q zprWE;U|^uFt!-#%Xl!h(si_I%>gnkLNqv2NpdgR|6o)f_Y$GEhh+-fE!UZzW09+iR z7nuuG3FN{>fJQ;|!bQN6AmWR)R};{QrX@js!3@mo0z#s4@(PLuX7&z_F8%@W2^skX z74;4M6K2evzhK3h^&2+q*t7q@$@3Sk-Ms(c;j8x_K242YBMbDpkEe@ch=k|d+PN--tsW3MGINp zCo5)7TU@a{W|sbpXDX9;4I-Sc%<&PGY@M~lGW*qu6?`gZJJ-ywZS|YEz%p!B+xDqU zuI?5EUIDK=e5@+(ZMZCGdUTy#)WSy;9Fc>CmeD(RZ>&TVB*KkdDoUFzME%kO>SOmmnvT-rPB(vd6WRfi8w zU*4)^(%Q?%H23Bq&*k&C+3)Sy6F;Mr#n(#9-r-8V-GK+%WwS1LBnY=7y%m|&ScI=)}fOVCxkn~ket%)TQA|rjKMom(?w$Zk{oA>DJ1EOZ!_4^hZ y%kJol-h16Pe#ceIze3k6*)KDT-MYi}cXJ&>^9H+B3mOB!$;Z>x&t;ucLK6V{TJ;S8 literal 0 HcmV?d00001 diff --git a/src/forge/Server/img/quality/folder.png b/src/forge/Server/img/quality/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..2ce02b8acf841ab556714f60e61422667bee38c9 GIT binary patch literal 390 zcmeAS@N?(olHy`uVBq!ia0vp^DnP8n!3-oB+Pk*{Nyg$JcPEB*=VV?2IUxZ)A+A8$ zq2Rwq#(&qW|E`(;T{8Z=WdC=|1R>FI;yV zXs&cgkY6x^fP#U&gMUDLLP32)|AhGq)^FH<;QWRA58i($n-UiYRJPO8#WBR<^w}$S z`I;0M+8*+EacvG-wC3LL{pb1qm``52^;-3_8W9N|&vi@QFS%*6_}lA!q1iG5pSB(U z(S7Qw#(`D;bB`@qtHODx;DdC<^|F>#jz0w~HGVH=y2BIHQha@BrL}9B)#}ZauAe50 z7R_{g?87qO$tXZ&qSD9r6ZS6+H{V_^ue5u6rU*yol!!IK7CyorF5~BdH?Et1@kt!DhjSk*!CId2?kGBKbLh*2~7Z= Cl&-`8 literal 0 HcmV?d00001 diff --git a/src/forge/Server/img/quality/high.png b/src/forge/Server/img/quality/high.png new file mode 100644 index 0000000000000000000000000000000000000000..ce67e47398c70c1fbc7ab177851b791efc1b6ab9 GIT binary patch literal 417 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|5(0ceT!Hj` zBG5gUnuh`cj};W28W=oxaCjLI@V21feM7_N1q;4z*zo?*dUVJ!GEZ&dw24Qx0!)=; z7dQC&l}?`M*(ZBGWykd%n{Pe|CHWGp&CNOm7Y(>1=SiHG&Htdq@8i_>eU0`W{W(Y5 af3mOrCClwnyQ>`NKL$@%KbLh*2~7Z@EwJ?f literal 0 HcmV?d00001 diff --git a/src/forge/Server/img/quality/label.png b/src/forge/Server/img/quality/label.png new file mode 100644 index 0000000000000000000000000000000000000000..e2c06300e34c0bf32506401efb0bba6524af76ae GIT binary patch literal 270 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv#^NA%Cx&(BWL^R}S^+*Gu0YxV z42+O~F$@63fJ_Lfae2>2plaTdAirP+0fmBw3G)|h*ni;sg=;KNHv=UqJzX3_G|nd{ zED(ONmz$;YxB!#Fzq{E+oGX}4c)eSsy|cP}qFC3Y-Q}VZfm816)jrUYbn5PIVbwsN zo!ra@ss)TQCPnS&xE#8H!Fm@%%*oQ1Ol%^mt+x~XR=b8W1UCgR1U4;USi};^=)u{= mG(jkYm4|2D8Ag*HGluY{+Lzygp5FvIg2B_(&t;ucLK6VXQC2tr literal 0 HcmV?d00001 diff --git a/src/forge/Server/img/quality/low.png b/src/forge/Server/img/quality/low.png new file mode 100644 index 0000000000000000000000000000000000000000..25c563d8e68e8849a5314d4eb6fa84ad7bfbb932 GIT binary patch literal 363 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|oC178T!D1| zeg_zG>DlkxchI?azY`Gk9|VzoK=uJ3>D+g~8OR2b5ZS&1Kv|IB1H02dfToC+1o;Is z2q+jh1jHv4)Hh6+zhM7?^A{ex|FC@429Sbno-U3d7N_@4zAbh{fX7wSN_Ovx+yDQ+ zda=e$xHr7AtJG|wMT>-V(Z6XkQr*qm_dUMQvu4#F_bb`eXH=4kI~WyNPH-$ts25=B z=vZxW<5@tR0V_vCSA|?bsohL=BcC^yna}XvkV`nuSa34+llX(>OLwYliQE+0vDM`I z7S82)8}7|^yc(GAa{T>EgN@rK@0(|B%krXghL>~t^OI7`1UjSi_SUSHxi&YcX03eu k%X`NSx3x@c{{NbP(N$&r^K(*m1AV~Y>FVdQ&MBb@001?ey#N3J literal 0 HcmV?d00001 diff --git a/src/forge/Server/img/quality/medium.png b/src/forge/Server/img/quality/medium.png new file mode 100644 index 0000000000000000000000000000000000000000..1b8814cecaa7854bcec627100008faba2ac99c0a GIT binary patch literal 339 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|j01c^T!Hle zeu3|O65snJzV?ej7~lHE!Cat-#MeFvAop9J__uzEZy>hBlQV`>fZ7B~g8YIR1neCG z;u8w$8|E)qzhVD@rH>cX0OdA$x;TbdoZcIJR_c%f&+2Jc6OW(&ZNIkN(4+BXnW4I! zP=*xO#rX$M@6tPO9JVcH!_J!zXE08z{x{XS;hVwBq%o z)SIn)`o0Q;v>UkPK2+;(^|(I&_wxfj?tbribp@rDcdFYTzG~BC{kQ7T-Vb}<|6*jH WAn#xQt5O~4dInEdKbLh*2~7aaLXOY? literal 0 HcmV?d00001 diff --git a/src/forge/Server/img/quality/repeat.png b/src/forge/Server/img/quality/repeat.png new file mode 100644 index 0000000000000000000000000000000000000000..2b0bad6c0ebcf724f68a858458ecd9f42d1e6b64 GIT binary patch literal 439 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k3?#4J%UA`Z7>k44ofy`glX(f`ga`P9xB_Vd z7%((6gfNZ4z{to5NCQbU0FlL|1|ohVg8MhnXql2AzhDLd1p|8r|A6>}{DS(1{t5FJ ztlzNz!1)XJAH4tIw0h?kpt@(CE{-7*l4lQIENWH|VLed1&`ii%*(s^z^Z)yiw>di| z&a~NjIrdLu+(XsVD=sXsciPeZ;ifjj#5n~En7eORK3H{F`0!%sZ#>aFS+cViEhtK# zee~EQpL1o}uZ6X_T%RNbWE?-FXk60N^_Xqu{sn3iZzg?E;InSCf7TtSu_fWTOPs#? z!TqWgi_R-%yzyx7vhts(-96WH!q>-h`K7iNx;=|Lv*N@N#+zr7L_}&;SR;>n{ivvq z{+g`va>K_QZ!YPnH%u0!-F&oR`-c+`J&#{~w9`|JeclrW!*{3h>jY$09#N@yY9jq- am%h+3EiV1ZzfS>u%;4$j=d#Wzp$P!=-Kg{c literal 0 HcmV?d00001 diff --git a/src/forge/Server/img/quality/vulnerabilities.png b/src/forge/Server/img/quality/vulnerabilities.png new file mode 100644 index 0000000000000000000000000000000000000000..6030bdd2651122cf7acbe799bb358674231e82d5 GIT binary patch literal 337 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|TmyVUT!FL! z5-BMbl$2a;$q5b?J<_l^P$5-SPv3uX{dFmMQnPbjEwm@t3AhW!UF+<)-?Lql53 z1E7i-o-U3d7N^fnKFD|2fWvw2G@a;OW|?At{?Ffans2V#i_D-}r%%F6$_=YEZM`e+ zv~4q)q;fmwxADo^lh!vEbM9He#IPu5n!M-FkgZ*eDXaE7rv23x@XmbmbjA8mX5QB1 zHZcV?fv4}7v^P{W_;K82HZpj+#@T_HLFX~w-YJ|XWrGZN8y9e&J+F9m@`7!Sla_CH gSY_{Z{pT@ry{%GmxhEYYfNo^)boFyt=akR{0Q}u^>Hq)$ literal 0 HcmV?d00001 diff --git a/src/forge/Server/quality/detail.jsx b/src/forge/Server/quality/detail.jsx index 252dd0dab..172f9f122 100644 --- a/src/forge/Server/quality/detail.jsx +++ b/src/forge/Server/quality/detail.jsx @@ -2,40 +2,40 @@ import React, {useState, useEffect, useRef } from "react"; import { getList, getCodeSnippets, getProblemDetail } from "../api"; import './detail.scss'; import ProblemDetail from "./component/ProblemDetail"; -import { issueType } from "../constants/quality" +import { issueType, issueTypeIcon } from "../constants/quality" +import folderImg from '../img/quality/folder.png' +import backImg from '../img/quality/back.png' +import { Divider } from 'antd'; function issueDetail(props){ const { owner , projectsId } = props.match.params; const [ snippetsDetail, setSnippetsDetail ] = useState(null); - const [ issues, setIssues ] = useState(null); + const [ issues, setIssues ] = useState({}); const [ selectedIssue, setSelectedIssue ] = useState(null); const [ selectedFlow, setSelectedFlow ] = useState(null); const [ detailVisible, setDetailVisible ] = useState(true); const [ issueInfo, setIssueInfo ] = useState(null); const [reload, setReload] = useState(); - const { projectDetail } = props; + const [pageNum, setPageNum] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [total, setTotal] = useState() + const { projectDetail, history } = props; const codeRef = useRef(null); useEffect(()=>{ // 更新网页标题 if(projectDetail){ - const { author, name} = projectDetail; - document.title = `reposyncer-${author.name}/${name}`; + if (history.location.state?.baseInfo) { + handleIssues(history.location.state?.baseInfo) + } else { + getIssueList() + } + if (history.location.state?.selectedIssue) { + setSelectedIssue(history.location.state?.selectedIssue) + } } - getList().then(res => { - let issues = res.issues - let issuesHandled = {} - issues.forEach(e => { - if (issuesHandled[e.component]) { - issuesHandled[e.component].push(e) - } else { - issuesHandled[e.component] = [e] - } - }); - setIssues(issuesHandled) - }) - }, [projectDetail]) + }, [projectDetail, pageNum]) useEffect(() => { if (selectedIssue) { @@ -49,7 +49,31 @@ function issueDetail(props){ useEffect(()=>{ },[reload]) - function addSpecialClassToRange(code, textRange, className) { + const getIssueList = (owner, name) => { + let params = { + p: pageNum, + ps: pageSize + }; + getList(params).then(res => { + handleIssues(res) + }) + }; + + const handleIssues = (data) => { + let newIssues = data.issues + let issuesHandled = JSON.parse(JSON.stringify(issues)) + setTotal(data.total) + newIssues.forEach(e => { + if (issuesHandled[e.component]) { + issuesHandled[e.component].push(e) + } else { + issuesHandled[e.component] = [e] + } + }); + setIssues(issuesHandled) + } + + const addSpecialClassToRange = (code, textRange, className) => { let textOnly = code.replace(/<[^>]+>/g, ''); // Remove HTML tags to get text only let start = 0; let end = textOnly.length; @@ -145,14 +169,16 @@ function issueDetail(props){ return
    setDetailVisible(false)}>
    + history.goBack()}/> + { issues && Object.entries(issues).map(([key, item]) =>
    -
    { key.split(`:`)[1] }
    +
    { key.split(`:`)[1] }
    { item.map(e => (
    setSelectedIssue(e)}>

    { e.message }

    -

    { issueType[e.type] }{ e.flows.length > 0 && +{e.flows.length} }

    +

    { issueType[e.type] }{ e.flows.length > 0 && +{e.flows.length} }

    { e.key === selectedIssue?.key && e.flows && e.flows.length > 0 && e.flows.map((flow, index) => { return
    selectAndJumpRepeat(flow.locations[0].textRange.startLine) }> @@ -167,10 +193,11 @@ function issueDetail(props){
    ) } + { pageNum * pageSize < total &&
    setPageNum(pageNum + 1)}>查看更多
    }
    { snippetsDetail &&
    -
    { snippetsDetail.component.path }
    +
    { snippetsDetail.component.path }
    {snippetsDetail.sources.map((item, index) => ( diff --git a/src/forge/Server/quality/detail.scss b/src/forge/Server/quality/detail.scss index e01990acc..a49bfb454 100644 --- a/src/forge/Server/quality/detail.scss +++ b/src/forge/Server/quality/detail.scss @@ -5,14 +5,42 @@ overflow-y: auto; width: 350px; flex-shrink: 0; + .back-icon { + width: 24px; + } + .ant-divider { + padding-right: 10px; + width: 330px; + } + .load-more { + text-align: center; + padding: 4px; + margin-right: 10px; + margin-right: 10px; + background: #FAFCFF; + border-radius: 3px 3px 3px 3px; + border: 1px solid rgba(42,97,255,0.23); + font-size: 14px; + color: #111010; + cursor: pointer; + } .doc-title { - width: 250px; - overflow: hidden; - white-space: nowrap; /* 防止文本换行 */ - overflow: hidden; /* 隐藏溢出的文本 */ - text-overflow: ellipsis; /* 显示省略号 */ - direction: rtl; /* 文字从右到左排列 */ - text-align: left; /* 文字从左边开始剪裁 */ + display: flex; + align-items: center; + img { + width: 18px; + margin-right: 10px; + } + span { + display: inline-block; + width: 240px; + overflow: hidden; + white-space: nowrap; /* 防止文本换行 */ + overflow: hidden; /* 隐藏溢出的文本 */ + text-overflow: ellipsis; /* 显示省略号 */ + direction: rtl; /* 文字从右到左排列 */ + text-align: left; /* 文字从左边开始剪裁 */ + } margin-top: 15px; margin-bottom: 20px; } @@ -23,7 +51,6 @@ border: 1px solid rgba(42,97,255,0.23); margin-bottom: 15px; margin-right: 10px; - font-size: 14px; word-break: break-word; font-family: PingFang SC, PingFang SC; font-size: 14px; @@ -32,6 +59,14 @@ &:hover { border: 1px solid #466AFF; } + .issue-typeline { + display: flex; + align-items: center; + img { + width: 14px; + margin-right: 5px; + } + } .issue-type { font-weight: 400; color: #5D6790; @@ -96,6 +131,12 @@ color: #111010; padding-left: 20px; line-height: 42px; + display: flex; + align-items: center; + img { + width: 18px; + margin-right: 10px; + } } .content { display: flex; @@ -174,36 +215,69 @@ border-radius: 4px 4px 4px 4px; border: 2px solid #FFFFFF; } + .count-title { + font-weight: bold; + font-size: 20px; + color: #333333; + margin: 28px 0; + } .issue-list { display: flex; .left-content { width: 218px; flex-shrink: 0; - .filter-title { - font-weight: bold; - font-size: 17px; - color: #000000; - padding: 20px; - } - .filter-item { - padding: 0 20px; - margin: 2px 0; - display: flex; - justify-content: space-between; - align-items: center; - height: 50px; + .clear-button { + margin: 17px 0 0 19px; + padding: 0 7px; + height: 28px; + border-radius: 3px 3px 3px 3px; + border: 1px solid #D4333F; + font-weight: 400; + font-size: 14px; + color: #D4333F; cursor: pointer; - &:hover { - background: #466bff0c; + } + .filters { + .filter-title { + font-weight: bold; + font-size: 17px; + color: #000000; + padding: 20px; + line-height: 28px; + .clear-button { + float: right; + margin: 0; + } + } + .filter-item { + padding: 0 20px; + margin: 2px 0; + display: flex; + justify-content: space-between; + align-items: center; + height: 50px; + cursor: pointer; + &:hover { + background: #466bff0c; + } + .item-type { + display: flex; + align-items: center; + img { + width: 16px; + margin-right: 5px; + } + } + } + .selected-item { + background: #466bff17; + } + .ant-divider { + margin: 20px; + margin-bottom: 0px; + width: auto; + min-width: unset; } - } - .selected-item { - background: #466bff17; - } - .ant-divider { - margin: 20px; - width: auto; - min-width: unset; } } .right-content { @@ -228,6 +302,7 @@ font-size: 14px; color: #5D6790; margin-top: 10px; + display: flex; > span:nth-child(2), > span:nth-child(3) { &::before { content: "|"; @@ -237,7 +312,7 @@ } } span:nth-of-type(n + 4) { - float: right; + margin-left: auto; } } .issue-message { @@ -261,6 +336,11 @@ } .total-info { padding: 20px; + p { + font-weight: bold; + font-size: 18px; + color: #333333; + } .info-list { display: flex; .total-item { @@ -270,6 +350,7 @@ font-weight: 400; font-size: 15px; color: #525151; + margin-top: 18px; > span:nth-child(2) { font-weight: bold; font-size: 18px; @@ -295,10 +376,48 @@ left: -40px; content: ''; width: 0px; - height: 53px; + height: 100%; border-left: 1px solid rgba(187,189,197,0.61); } } } } +} + +.headBox{ + height:60px; + line-height: 60px; + background-color:#fafcff; + border:1px solid rgba(42, 97, 255, 0.23); + border-radius:3px 3px 0px 0px; + color:#333333; + position: relative; +} + +.nullStoreBox{ + background-color:#fafcff; + border-radius:4px 4px 0px 0px; + text-align: center; + color:#333333; + padding-bottom: 65px; + .loBox{width: 68px;} + .introBox{ + color:#666666; + width: 57%; + margin: 15px auto; + } + .borBox{ + width: 45%; + margin: 0 auto 20px; + border-bottom: 1px solid rgba(90, 117, 193, 0.23); + } +} + +.icon-text { + display: flex; + align-items: center; + img { + width: 16px; + margin-right: 5px; + } } \ No newline at end of file diff --git a/src/forge/Server/quality/index.jsx b/src/forge/Server/quality/index.jsx index 95d6696b5..f1b4de18d 100644 --- a/src/forge/Server/quality/index.jsx +++ b/src/forge/Server/quality/index.jsx @@ -1,38 +1,48 @@ -import React, {useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, Fragment } from "react"; import { getList, getCodeSnippets, getProblemDetail, getProblemData } from "../api"; import './detail.scss'; import ProblemDetail from "./component/ProblemDetail"; -import { issueType, issueTypeKeys, maintainability } from "../constants/quality" -import { Divider } from 'antd'; -import { formatNumber } from '../../Utils/utils' +import { issueType, maintainability, issueTypeIcon, maintainabilityIcon } from "../constants/quality"; +import { Divider, Pagination, Button } from 'antd'; +import { formatNumber, formatNumberWithCommas } from '../../Utils/utils'; +import logo from '../img/logo4-1.png'; +import labelIcon from '../img/quality/label.png'; +import code_lines from '../img/quality/code_lines.png'; +import repeat from '../img/quality/repeat.png'; +import cover from '../img/quality/cover.png'; +import { Link } from "react-router-dom"; + +function IssueList(props) { + const [issues, setIssues] = useState({}); + const [baseInfo, setBaseInfo] = useState({}); + const [baseData, setBaseData] = useState({}); + const [listFilter, setListFilter] = useState({}); + const [selectedIssue, setSelectedIssue] = useState(null); + const [detailVisible, setDetailVisible] = useState(true); + const [issueInfo, setIssueInfo] = useState({}); + const [selectedType, setSelectedType] = useState([]); + const [selectedSeverity, setSelectedSeverity] = useState([]); + const [selectedTag, setSelectedTag] = useState([]); + const [pageNum, setPageNum] = useState(1); + const [pageSize, setPageSize] = useState(10); + const { projectDetail, history } = props; + const metricKeys = 'lines,coverage,lines_to_cover,duplicated_lines_density,maintainability_issues,vulnerabilities,bugs,code_smells'; + const facets = 'impactSeverities,tags,types'; + const { owner , projectsId } = props.match.params; -function issueList(props){ - const [ issues, setIssues ] = useState({}); - const [ baseInfo, setBaseInfo ] = useState({}); - const [ baseData, setBaseData ] = useState({}); - const [ listFilter, setListFilter ] = useState({}); - const [ selectedIssue, setSelectedIssue ] = useState(null); - const [ detailVisible, setDetailVisible ] = useState(true); - const [ issueInfo, setIssueInfo ] = useState({}); - const [ selectedType, setSelectedType ] = useState([]); - const [ selectedSeverity, setSelectedSeverity ] = useState([]); - const [ selectedTag, setSelectedTag ] = useState([]); - const { projectDetail } = props; - const metricKeys = 'lines,coverage,lines_to_cover,duplicated_lines_density,maintainability_issues,vulnerabilities,bugs,code_smells' - const facets = 'impactSeverities,tags,types' useEffect(() => { - let newContainer = document.getElementsByClassName("newContainer")[0] - newContainer.style.backgroundColor = '#F7F9FCFF' + let newContainer = document.getElementsByClassName("newContainer")[0]; + newContainer.style.backgroundColor = '#F7F9FCFF'; return () => { - newContainer.style.backgroundColor = '#FFFFFF' - } - },[]) + newContainer.style.backgroundColor = '#FFFFFF'; + }; + }, []); - useEffect(()=>{ - // 更新网页标题 - if(projectDetail){ - const { author, name} = projectDetail; - document.title = `reposyncer-${author.name}/${name}`; + useEffect(() => { + if (projectDetail) { + const { author, name } = projectDetail; + document.title = `代码质量分析-${author.name}/${name}`; + getIssueList(author.name, name); } getProblemData('kingchanx-fluid-cloudnative_fluid', metricKeys).then(res => { const measures = res.component.measures; @@ -40,181 +50,195 @@ function issueList(props){ measures.forEach(measure => { result[measure.metric] = measure.value; }); - setBaseData(result) - }) - getIssueList() - }, [projectDetail]) + setBaseData(result); + }); + }, [projectDetail]); - function getIssueList() { + const getIssueList = (owner, name) => { let params = { facets: facets, types: selectedType.join(','), impactSeverities: selectedSeverity.join(','), tags: selectedTag.join(','), - } - getList(params).then(res => { - let issues = res.issues - let issuesHandled = {} - issues.forEach(e => { - if (issuesHandled[e.component]) { - issuesHandled[e.component].push(e) + p: pageNum, + ps: pageSize + }; + getList(params, owner, name).then(res => { + const issues = res.issues.reduce((acc, issue) => { + if (acc[issue.component]) { + acc[issue.component].push(issue); } else { - issuesHandled[e.component] = [e] + acc[issue.component] = [issue]; } - }); - const filter = {} - res.facets.forEach(e => { - filter[e.property] = e.values - }) - setListFilter(filter) - setBaseInfo(res) - setIssues(issuesHandled) - }) - } + return acc; + }, {}); + + const filter = res.facets.reduce((acc, facet) => { + acc[facet.property] = facet.values; + return acc; + }, {}); + + setListFilter(filter); + setBaseInfo(res); + setIssues(issues); + }); + }; useEffect(() => { - getIssueList() - },[selectedType, selectedSeverity, selectedTag]) + setPageNum(1) + }, [selectedType, selectedSeverity, selectedTag]); - function detail(e) { - setSelectedIssue(e) + useEffect(() => { + getIssueList(); + }, [pageNum, pageSize]); + + const detail = (e) => { + setSelectedIssue(e); getProblemDetail(e.rule).then(res => { - setIssueInfo(res?.rule) - setDetailVisible(true) + setIssueInfo(res?.rule); + setDetailVisible(true); + }); + }; + + const toggleSelection = (state, setter, value) => { + const index = state.indexOf(value); + if (index > -1) { + state.splice(index, 1); + setter([...state]); + } else { + setter([...state, value]); + } + }; + + const renderFilterItems = (items, state, setter, label) => ( +
    +
    + {label} + { state.length > 0 && } +
    + {items && items.map(e => ( +
    toggleSelection(state, setter, e.val)} + > + {label === "问题类型" ? issueType[e.val] : maintainability[e.val] || e.val} + {e.count} +
    + ))} + +
    + ); + + const renderTotalItem = (label, value, icon) => ( +
    + {label} + {value} +
    + ); + + const renderDetailedItem = (label, value, percent, moreValue, icon) => ( +
    + {label} + + {percent}% + {moreValue}{formatNumber(value)} + +
    + ); + + const toDetail = (e) => { + history.push({ + pathname: `/${owner}/${projectsId}/service/quality/detail`, + state: { + baseInfo, + selectedIssue: e + } }) } - function selectType(e) { - let index = selectedType.indexOf(e) - if (index > -1) { - selectedType.splice(index, 1) - setSelectedType([...selectedType]) - } else { - setSelectedType([...selectedType, e]) - } - } - - function selectSeverity(e) { - let index = selectedSeverity.indexOf(e) - if (index > -1) { - selectedSeverity.splice(index, 1) - setSelectedSeverity([...selectedSeverity]) - } else { - setSelectedSeverity([...selectedSeverity, e]) - } - } - - function selectTags(e) { - let index = selectedTag.indexOf(e) - if (index > -1) { - selectedTag.splice(index, 1) - setSelectedTag([...selectedTag]) - } else { - setSelectedTag([...selectedTag, e]) - } - } - - return
    - setDetailVisible(false)}> -
    -

    代码分析结果

    -
    -
    - Bugs - { baseData.bugs } -
    -
    - 漏洞 - { formatNumber(baseData.vulnerabilities) } -
    -
    - 异味 - { formatNumber(baseData.code_smells) } -
    -
    - 重复率 - { baseData.duplicated_lines_density }% 代码{ formatNumber(baseData.lines) } -
    -
    - 覆盖率 - { baseData.coverage }% 代码{ formatNumber(baseData.lines_to_cover) } -
    -
    - 代码行数 - { baseData.lines && baseData.lines.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") } -
    -
    -
    - {issues &&

    发现{ baseInfo.total }个问题

    } -
    -
    -
    -
    - 问题类型 {} + return ( +
    + setDetailVisible(false)} + /> + { + false && +
    sonar代码质量分析工具
    +
    + +

    欢迎使用代码质量分析工具

    +
    代码质量分析工具通过静态代码分析帮助开发者识别和修复代码库潜在的缺陷、代码异味、复杂度过高的代码段以及潜在的安全漏洞。同时能够评估代码的结构和风格,提供关于代码覆盖率、重复率等关键指标的深入洞察。
    +
    +
    - { - listFilter.types && listFilter.types.map(e => ( -
    selectType(e.val)}> - { issueType[e.val] } - { e.count } -
    )) - } - -
    -
    -
    - 严重程度 -
    - { - listFilter.impactSeverities && listFilter.impactSeverities.map(e => ( -
    selectSeverity(e.val)}> - { maintainability[e.val] } - { e.count } -
    )) - } - -
    -
    -
    - 标签 -
    - { - listFilter.tags && listFilter.tags.map(e => ( -
    selectTags(e.val)}> - { e.val } - { e.count } -
    )) - } -
    -
    -
    - { - issues && Object.entries(issues).map(([key, item]) =>
    -
    { key.split(`:`)[1] }
    - { - item.map(e => ( -
    setSelectedIssue(e)}> -

    { e.message } detail(e)}>问题分析L{ e.line }

    -

    - { issueType[e.type] } - { maintainability[e.impacts[0].severity] } - { e.debt }工作 - - { - e.tags && e.tags.map((tag, index) => ( - { tag }{ index < e.tags.length - 1 ? ',' : '' } - )) - } - -

    + + } + { + true &&
    +
    +

    代码分析结果

    +
    + {renderTotalItem("Bugs", baseData.bugs, issueTypeIcon.BUG)} + {renderTotalItem("漏洞", formatNumber(baseData.vulnerabilities), issueTypeIcon.VULNERABILITY)} + {renderTotalItem("异味", formatNumber(baseData.code_smells), issueTypeIcon.CODE_SMELL)} + {renderDetailedItem("重复率", baseData.lines, baseData.duplicated_lines_density, "代码", repeat)} + {renderDetailedItem("覆盖率", baseData.lines_to_cover, baseData.coverage, "代码", cover)} + {renderTotalItem("代码行数", formatNumberWithCommas(baseData.lines), code_lines)} +
    - )) - } -
    - ) - } -
    + {issues &&
    发现{ baseData.maintainability_issues && JSON.parse(baseData.maintainability_issues).total }个问题
    } +
    +
    + { (selectedType.length > 0 || selectedSeverity.length > 0 || selectedTag .length > 0) && } + {renderFilterItems(listFilter.types, selectedType, setSelectedType, "问题类型")} + {renderFilterItems(listFilter.impactSeverities, selectedSeverity, setSelectedSeverity, "严重程度")} + {renderFilterItems(listFilter.tags, selectedTag, setSelectedTag, "标签")} +
    +
    + {issues && Object.entries(issues).map(([key, item]) => ( +
    +
    + {key.split(':')[1]} +
    + {item.map(e => ( +
    toDetail(e)} + > +

    + {e.message} + detail(e)}>问题分析 + L{e.line} +

    +

    + {issueType[e.type]} + {maintainability[e.impacts[0].severity]} + {e.debt}工作 + + {e.tags && e.tags.length > 0 && } + {e.tags && e.tags.map((tag, index) => ( + + {tag}{index < e.tags.length - 1 ? ',' : ''} + + ))} + +

    +
    + ))} +
    + ))} + setPageNum(e)} onShowSizeChange={(current, size) => setPageSize(size)} /> +
    +
    +
    + }
    -
    + ); } -export default issueList; \ No newline at end of file + +export default IssueList; diff --git a/src/forge/Server/reposyncer/component/storeList.jsx b/src/forge/Server/reposyncer/component/storeList.jsx index 0580435ef..86992e01b 100644 --- a/src/forge/Server/reposyncer/component/storeList.jsx +++ b/src/forge/Server/reposyncer/component/storeList.jsx @@ -2,7 +2,7 @@ import React, { Fragment, useEffect, useState} from "react"; import { Button, Modal, Icon, message, Select, Input, Form, Radio, Table, Divider, Badge, Spin, Checkbox } from "antd"; import { Link } from "react-router-dom"; import gitlinkLogo from '../../img/gitlink.jpg'; -import logo from '../../img/logo3.png'; +import logo from '../../img/logo3-1.png'; import '../index.scss'; import axios from "axios"; import AddTaskModal from './addTaskModal'; diff --git a/src/forge/Utils/utils.js b/src/forge/Utils/utils.js index 6aa8c57f7..085bfd5c6 100644 --- a/src/forge/Utils/utils.js +++ b/src/forge/Utils/utils.js @@ -9,4 +9,8 @@ export function formatNumber(num) { } else { return num.toString(); // 小于 1000 的数字原样返回 } +} +export function formatNumberWithCommas(num) { + if (num === undefined || num === null) return + return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); } \ No newline at end of file From 0e3aeda1cfff116ef0b1502bd0e4291589acf6a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=BF=83=E5=AE=87?= Date: Fri, 7 Jun 2024 16:48:06 +0800 Subject: [PATCH 5/8] cont. --- package.json | 2 +- src/AppConfig.js | 2 +- src/forge/Server/api.js | 7 +++++++ src/forge/Server/quality/index.jsx | 13 +++++++++---- src/services/project.js | 4 +++- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 7a1c3232d..d6b36ce01 100644 --- a/package.json +++ b/package.json @@ -200,7 +200,7 @@ "eslintConfig": { "extends": "react-app" }, - "proxy": "http://172.20.32.202:9999", + "proxy": "http://172.20.32.202:4000", "port": "3007", "devDependencies": { "@babel/core": "^7.23.6", diff --git a/src/AppConfig.js b/src/AppConfig.js index e102edbe1..308ff1a6a 100644 --- a/src/AppConfig.js +++ b/src/AppConfig.js @@ -32,7 +32,7 @@ export function initAxiosInterceptors(props) { // 判断网络是否连接 initOnlineOfflineListener(); - var proxy = "https://testforgeplus.trustie.net"; + var proxy = "http://172.20.32.202:4000/"; // var proxy = "https://www.gitlink.org.cn"; //响应前的设置 diff --git a/src/forge/Server/api.js b/src/forge/Server/api.js index d5daf53de..52dc9dca4 100644 --- a/src/forge/Server/api.js +++ b/src/forge/Server/api.js @@ -35,3 +35,10 @@ export function getProblemData(component, keys) { }); } +// 初始化sonar +export function initSonar(open, owner, repo) { + return fetch({ + url: `/api/v1/${owner}/${repo}/sonarqubes/sonar_initialize.json?has_actions=${ open }`, + method: 'post', + }); +} \ No newline at end of file diff --git a/src/forge/Server/quality/index.jsx b/src/forge/Server/quality/index.jsx index f1b4de18d..f1fae6bec 100644 --- a/src/forge/Server/quality/index.jsx +++ b/src/forge/Server/quality/index.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, Fragment } from "react"; -import { getList, getCodeSnippets, getProblemDetail, getProblemData } from "../api"; +import { getList, getCodeSnippets, getProblemDetail, getProblemData, initSonar } from "../api"; import './detail.scss'; import ProblemDetail from "./component/ProblemDetail"; import { issueType, maintainability, issueTypeIcon, maintainabilityIcon } from "../constants/quality"; @@ -25,6 +25,7 @@ function IssueList(props) { const [selectedTag, setSelectedTag] = useState([]); const [pageNum, setPageNum] = useState(1); const [pageSize, setPageSize] = useState(10); + const [isopen, setIsopen] = useState(false); const { projectDetail, history } = props; const metricKeys = 'lines,coverage,lines_to_cover,duplicated_lines_density,maintainability_issues,vulnerabilities,bugs,code_smells'; const facets = 'impactSeverities,tags,types'; @@ -157,6 +158,10 @@ function IssueList(props) { }) } + const openAnalysis = () => { + initSonar(true, owner, projectsId) + } + return (
    setDetailVisible(false)} /> { - false && + !isopen &&
    sonar代码质量分析工具

    欢迎使用代码质量分析工具

    代码质量分析工具通过静态代码分析帮助开发者识别和修复代码库潜在的缺陷、代码异味、复杂度过高的代码段以及潜在的安全漏洞。同时能够评估代码的结构和风格,提供关于代码覆盖率、重复率等关键指标的深入洞察。
    - +
    } { - true &&
    + isopen &&

    代码分析结果

    diff --git a/src/services/project.js b/src/services/project.js index 0cbe82d56..694cdaac1 100644 --- a/src/services/project.js +++ b/src/services/project.js @@ -6,7 +6,9 @@ let baseZoneUrl = `${zoneUrl}/api` if (__CLIENT__ ) { let settings = localStorage.chromesetting && localStorage.chromesetting !== 'undefined' &&JSON.parse(localStorage.chromesetting); - baseUrl = settings ? `${settings && settings.common.main_site_url}/api` : '/api'; + // 测试待删 + // baseUrl = settings ? `${settings && settings.common.main_site_url}/api` : '/api'; + baseUrl = settings ? `http://172.20.32.202:4000/api` : '/api'; baseZoneUrl = settings ? `${settings && settings.common.zone}/api` : `${zoneUrl}/api`; } From b4a0f9795c6884b34f100d724009859738e607d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=BF=83=E5=AE=87?= Date: Tue, 11 Jun 2024 16:47:13 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E8=81=94=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/forge/Server/api.js | 43 ++++- src/forge/Server/constants/quality.js | 2 +- src/forge/Server/quality/detail.jsx | 4 +- src/forge/Server/quality/detail.scss | 7 + src/forge/Server/quality/index.jsx | 258 ++++++++++++++++---------- src/forge/javaFetch.js | 4 +- src/services/project.js | 5 +- 7 files changed, 212 insertions(+), 111 deletions(-) diff --git a/src/forge/Server/api.js b/src/forge/Server/api.js index 52dc9dca4..acf0679af 100644 --- a/src/forge/Server/api.js +++ b/src/forge/Server/api.js @@ -3,8 +3,8 @@ import fetch from './fetch'; // 获取列表 export function getList(params, owner, repo) { return fetch({ - // url: `/api/v1/${owner}/${repo}/sonarqubes/issues_search.json`, - url: `/api/issues/search?components=kingchanx-fluid-cloudnative_fluid&s=FILE_LINE&issueStatuses=CONFIRMED%2COPEN&&additionalFields=_all&timeZone=Asia%2FShanghai`, + url: `/api/v1/${owner}/${repo}/sonarqubes/issues_search.json?s=FILE_LINE`, + // url: `/api/issues/search?components=kingchanx-fluid-cloudnative_fluid&s=FILE_LINE&issueStatuses=CONFIRMED%2COPEN&&additionalFields=_all&timeZone=Asia%2FShanghai`, method: 'get', params }); @@ -12,25 +12,25 @@ export function getList(params, owner, repo) { // 代码片段 -export function getCodeSnippets(key) { +export function getCodeSnippets(key, owner, repo) { return fetch({ - url: `/api/sources/issue_snippets?issueKey=${ key }`, + url: `/api/v1/${owner}/${repo}/sonarqubes/sources_issue_snippet.json?issueKey=${ key }`, method: 'get', }); } // 问题分析 -export function getProblemDetail(key) { +export function getProblemDetail(key, owner, repo) { return fetch({ - url: `/api/rules/show?key=${ key }`, + url: `/api/v1/${owner}/${repo}/sonarqubes/rules_show.json?key=${ key }`, method: 'get', }); } // 数据汇总 -export function getProblemData(component, keys) { +export function getProblemData(owner, repo , keys) { return fetch({ - url: `/api/measures/component?component=${ component }&metricKeys=${ keys }`, + url: `/api/v1/${owner}/${repo}/sonarqubes/measures_component.json?component=${ owner }-${ repo }&metricKeys=${ keys }`, method: 'get', }); } @@ -41,4 +41,29 @@ export function initSonar(open, owner, repo) { url: `/api/v1/${owner}/${repo}/sonarqubes/sonar_initialize.json?has_actions=${ open }`, method: 'post', }); -} \ No newline at end of file +} + +// 开始分析 +export function startAlalyze( branch , owner, repo) { + return fetch({ + url: `/api/v1/${owner}/${repo}/sonarqubes/insert_file.json?branch=${ branch }`, + method: 'post', + }); +} + +// // 获取action任务列表 +// export function getActionList(owner, repo ) { +// return fetch({ +// url: `/api/v1/${owner}/${repo}/actions`, +// method: 'get', +// }); +// } + +// 获取action任务列表 +export function getActionList(owner, repo ) { + return fetch({ + url: `/api/v1/${owner}/${repo}/actions/runs?workflow=SonarScanner.yaml`, + method: 'get', + }); +} + diff --git a/src/forge/Server/constants/quality.js b/src/forge/Server/constants/quality.js index 2cf44037a..c44461377 100644 --- a/src/forge/Server/constants/quality.js +++ b/src/forge/Server/constants/quality.js @@ -32,4 +32,4 @@ export const maintainabilityIcon = { LOW: low, MEDIUM: medium, HIGH: high -} \ No newline at end of file +} diff --git a/src/forge/Server/quality/detail.jsx b/src/forge/Server/quality/detail.jsx index 172f9f122..8adbadd68 100644 --- a/src/forge/Server/quality/detail.jsx +++ b/src/forge/Server/quality/detail.jsx @@ -39,7 +39,7 @@ function issueDetail(props){ useEffect(() => { if (selectedIssue) { - getCodeSnippets(selectedIssue.key).then(res => { + getCodeSnippets(selectedIssue.key, owner, projectsId).then(res => { processIssues(res[selectedIssue.component].sources, issues[selectedIssue.component]) setSnippetsDetail(res[selectedIssue.component]); }) @@ -160,7 +160,7 @@ function issueDetail(props){ } function detail() { - getProblemDetail(selectedIssue.rule).then(res => { + getProblemDetail( selectedIssue.rule, owner, projectsId).then(res => { setIssueInfo(res?.rule) setDetailVisible(true) }) diff --git a/src/forge/Server/quality/detail.scss b/src/forge/Server/quality/detail.scss index a49bfb454..69991a2a9 100644 --- a/src/forge/Server/quality/detail.scss +++ b/src/forge/Server/quality/detail.scss @@ -169,6 +169,13 @@ margin-bottom: 10px; cursor: pointer; word-break: break-word; + span { + font-weight: 400; + font-size: 14px; + color: #466AFF; + margin-left: 16px; + cursor: pointer; + } } .selected-issue { border: 1px solid rgb(253, 162, 155); diff --git a/src/forge/Server/quality/index.jsx b/src/forge/Server/quality/index.jsx index f1fae6bec..5549b0c03 100644 --- a/src/forge/Server/quality/index.jsx +++ b/src/forge/Server/quality/index.jsx @@ -1,9 +1,9 @@ import React, { useState, useEffect, Fragment } from "react"; -import { getList, getCodeSnippets, getProblemDetail, getProblemData, initSonar } from "../api"; +import { getList, getProblemDetail, getProblemData, initSonar, startAlalyze, getActionList } from "../api"; import './detail.scss'; import ProblemDetail from "./component/ProblemDetail"; import { issueType, maintainability, issueTypeIcon, maintainabilityIcon } from "../constants/quality"; -import { Divider, Pagination, Button } from 'antd'; +import { Divider, Pagination, Button, message, Spin } from 'antd'; import { formatNumber, formatNumberWithCommas } from '../../Utils/utils'; import logo from '../img/logo4-1.png'; import labelIcon from '../img/quality/label.png'; @@ -12,6 +12,15 @@ import repeat from '../img/quality/repeat.png'; import cover from '../img/quality/cover.png'; import { Link } from "react-router-dom"; + +const actionStatus = { + success: 1, + failure: 2, + waiting: 5, + running: 6, + none: 7 +} + function IssueList(props) { const [issues, setIssues] = useState({}); const [baseInfo, setBaseInfo] = useState({}); @@ -25,7 +34,9 @@ function IssueList(props) { const [selectedTag, setSelectedTag] = useState([]); const [pageNum, setPageNum] = useState(1); const [pageSize, setPageSize] = useState(10); - const [isopen, setIsopen] = useState(false); + const [loading, setLoading] = useState(false); + const [pageLoading, setPageLoading] = useState(true); + const [analysisStatus, setAnalysisStatus] = useState(status.none); const { projectDetail, history } = props; const metricKeys = 'lines,coverage,lines_to_cover,duplicated_lines_density,maintainability_issues,vulnerabilities,bugs,code_smells'; const facets = 'impactSeverities,tags,types'; @@ -43,19 +54,36 @@ function IssueList(props) { if (projectDetail) { const { author, name } = projectDetail; document.title = `代码质量分析-${author.name}/${name}`; - getIssueList(author.name, name); + setPageLoading(true) + getActionList(owner, projectsId).then(res => { + setPageLoading(false) + if (res.runs.length === 0) { + setAnalysisStatus(actionStatus.none) + } else { + setAnalysisStatus(res.runs[0].status) + if (res.runs[0].status) { + getData() + getIssueList() + } + } + }) } - getProblemData('kingchanx-fluid-cloudnative_fluid', metricKeys).then(res => { - const measures = res.component.measures; - const result = {}; - measures.forEach(measure => { - result[measure.metric] = measure.value; - }); - setBaseData(result); - }); }, [projectDetail]); - const getIssueList = (owner, name) => { + const getData = () => { + getProblemData(owner, projectsId, metricKeys).then(res => { + if (res.status === 0) { + const measures = res.component.measures; + const result = {}; + measures.forEach(measure => { + result[measure.metric] = measure.value; + }); + setBaseData(result); + } + }); + } + + const getIssueList = () => { let params = { facets: facets, types: selectedType.join(','), @@ -64,7 +92,9 @@ function IssueList(props) { p: pageNum, ps: pageSize }; - getList(params, owner, name).then(res => { + setPageLoading(true) + getList(params, owner, projectsId).then(res => { + setPageLoading(false) const issues = res.issues.reduce((acc, issue) => { if (acc[issue.component]) { acc[issue.component].push(issue); @@ -86,16 +116,22 @@ function IssueList(props) { }; useEffect(() => { - setPageNum(1) + if (pageNum === 1) { + getIssueList() + } else { + setPageNum(1) + } }, [selectedType, selectedSeverity, selectedTag]); useEffect(() => { - getIssueList(); + if (projectDetail && analysisStatus === actionStatus.success) { + getIssueList() + } }, [pageNum, pageSize]); const detail = (e) => { setSelectedIssue(e); - getProblemDetail(e.rule).then(res => { + getProblemDetail(e.rule, owner, projectsId).then(res => { setIssueInfo(res?.rule); setDetailVisible(true); }); @@ -159,90 +195,124 @@ function IssueList(props) { } const openAnalysis = () => { - initSonar(true, owner, projectsId) + setLoading(true) + initSonar(true, owner, projectsId).then(res => { + if (res.status === 0) { + startAlalyze(projectDetail.default_branch, owner, projectsId).then(res1=> { + if (res1.status === 0) { + message.success('创建分析成功,请等待分析完成') + setAnalysisStatus(actionStatus.waiting) + } + setLoading(false) + }) + } else { + setLoading(false) + } + }) + } + + const getStatusButton = () => { + switch (analysisStatus) { + case actionStatus.none: + return + case actionStatus.waiting: + case actionStatus.running: + return + case actionStatus.failure: + return
    +

    代码正在分析中,请重试

    + +
    + default: + return + } } return ( -
    - setDetailVisible(false)} - /> - { - !isopen && -
    sonar代码质量分析工具
    -
    - -

    欢迎使用代码质量分析工具

    -
    代码质量分析工具通过静态代码分析帮助开发者识别和修复代码库潜在的缺陷、代码异味、复杂度过高的代码段以及潜在的安全漏洞。同时能够评估代码的结构和风格,提供关于代码覆盖率、重复率等关键指标的深入洞察。
    -
    - -
    -
    - } - { - isopen &&
    -
    -

    代码分析结果

    -
    - {renderTotalItem("Bugs", baseData.bugs, issueTypeIcon.BUG)} - {renderTotalItem("漏洞", formatNumber(baseData.vulnerabilities), issueTypeIcon.VULNERABILITY)} - {renderTotalItem("异味", formatNumber(baseData.code_smells), issueTypeIcon.CODE_SMELL)} - {renderDetailedItem("重复率", baseData.lines, baseData.duplicated_lines_density, "代码", repeat)} - {renderDetailedItem("覆盖率", baseData.lines_to_cover, baseData.coverage, "代码", cover)} - {renderTotalItem("代码行数", formatNumberWithCommas(baseData.lines), code_lines)} + +
    + setDetailVisible(false)} + /> + { + analysisStatus !== actionStatus.success && +
    sonar代码质量分析工具
    +
    + +

    欢迎使用代码质量分析工具

    +
    代码质量分析工具通过静态代码分析帮助开发者识别和修复代码库潜在的缺陷、代码异味、复杂度过高的代码段以及潜在的安全漏洞。同时能够评估代码的结构和风格,提供关于代码覆盖率、重复率等关键指标的深入洞察。
    +
    + { + getStatusButton() + } +
    +
    + } + { + analysisStatus === actionStatus.success &&
    +
    +

    代码分析结果

    +
    + {renderTotalItem("Bugs", baseData.bugs, issueTypeIcon.BUG)} + {renderTotalItem("漏洞", formatNumber(baseData.vulnerabilities), issueTypeIcon.VULNERABILITY)} + {renderTotalItem("异味", formatNumber(baseData.code_smells), issueTypeIcon.CODE_SMELL)} + {renderDetailedItem("重复率", baseData.lines, baseData.duplicated_lines_density, "代码", repeat)} + {renderDetailedItem("覆盖率", baseData.lines_to_cover, baseData.coverage, "代码", cover)} + {renderTotalItem("代码行数", formatNumberWithCommas(baseData.lines), code_lines)} +
    -
    - {issues &&
    发现{ baseData.maintainability_issues && JSON.parse(baseData.maintainability_issues).total }个问题
    } -
    -
    - { (selectedType.length > 0 || selectedSeverity.length > 0 || selectedTag .length > 0) && } - {renderFilterItems(listFilter.types, selectedType, setSelectedType, "问题类型")} - {renderFilterItems(listFilter.impactSeverities, selectedSeverity, setSelectedSeverity, "严重程度")} - {renderFilterItems(listFilter.tags, selectedTag, setSelectedTag, "标签")} -
    -
    - {issues && Object.entries(issues).map(([key, item]) => ( -
    -
    - {key.split(':')[1]} -
    - {item.map(e => ( -
    toDetail(e)} - > -

    - {e.message} - detail(e)}>问题分析 - L{e.line} -

    -

    - {issueType[e.type]} - {maintainability[e.impacts[0].severity]} - {e.debt}工作 - - {e.tags && e.tags.length > 0 && } - {e.tags && e.tags.map((tag, index) => ( - - {tag}{index < e.tags.length - 1 ? ',' : ''} - - ))} - -

    + {issues &&
    发现{ baseData.maintainability_issues && JSON.parse(baseData.maintainability_issues).total }个问题
    } +
    +
    + { (selectedType.length > 0 || selectedSeverity.length > 0 || selectedTag .length > 0) && } + {renderFilterItems(listFilter.types, selectedType, setSelectedType, "问题类型")} + {renderFilterItems(listFilter.impactSeverities, selectedSeverity, setSelectedSeverity, "严重程度")} + {renderFilterItems(listFilter.tags, selectedTag, setSelectedTag, "标签")} +
    +
    + {issues && Object.entries(issues).map(([key, item]) => ( +
    +
    + {key.split(':')[1]}
    - ))} -
    - ))} - setPageNum(e)} onShowSizeChange={(current, size) => setPageSize(size)} /> + {item.map(e => ( +
    toDetail(e)} + > +

    + {e.message} + { ele.stopPropagation();detail(e)}}>问题分析 + L{e.line} +

    +

    + {issueType[e.type]} + {maintainability[e.impacts[0].severity]} + {e.debt}工作 + + {e.tags && e.tags.length > 0 && } + {e.tags && e.tags.map((tag, index) => ( + + {tag}{index < e.tags.length - 1 ? ',' : ''} + + ))} + +

    +
    + ))} +
    + ))} + setPageNum(e)} onShowSizeChange={(current, size) => setPageSize(size)} /> +
    -
    - } -
    + } +
    +
    ); } diff --git a/src/forge/javaFetch.js b/src/forge/javaFetch.js index 56ec563b7..f33e69891 100644 --- a/src/forge/javaFetch.js +++ b/src/forge/javaFetch.js @@ -6,9 +6,9 @@ import Login from './Wiki/components/Login'; export const TokenKey = 'autologin_trustie'; export default function javaFetch(actionUrl){ - // if (window.location.href.indexOf('localhost') < 0) { + if (window.location.href.indexOf('localhost') < 0) { axios.defaults.withCredentials = true; - // } + } // 创建axios实例 const service = axios.create({ diff --git a/src/services/project.js b/src/services/project.js index 694cdaac1..fa81bc0b5 100644 --- a/src/services/project.js +++ b/src/services/project.js @@ -6,9 +6,8 @@ let baseZoneUrl = `${zoneUrl}/api` if (__CLIENT__ ) { let settings = localStorage.chromesetting && localStorage.chromesetting !== 'undefined' &&JSON.parse(localStorage.chromesetting); - // 测试待删 - // baseUrl = settings ? `${settings && settings.common.main_site_url}/api` : '/api'; - baseUrl = settings ? `http://172.20.32.202:4000/api` : '/api'; + baseUrl = settings ? `${settings && settings.common.main_site_url}/api` : '/api'; + // baseUrl = settings ? `http://172.20.32.202:4000/api` : '/api'; baseZoneUrl = settings ? `${settings && settings.common.zone}/api` : `${zoneUrl}/api`; } From bf61dd37e77376f6eefbba7f5b61e31cc40c1baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=BF=83=E5=AE=87?= Date: Wed, 12 Jun 2024 08:44:13 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BC=BA=E7=9C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/forge/Server/quality/detail.scss | 16 ++++++++++++++++ src/forge/Server/quality/index.jsx | 9 +++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/forge/Server/quality/detail.scss b/src/forge/Server/quality/detail.scss index 69991a2a9..84a424690 100644 --- a/src/forge/Server/quality/detail.scss +++ b/src/forge/Server/quality/detail.scss @@ -216,6 +216,21 @@ } } .issue-total { + .nodata { + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + img { + width: 400px; + } + p { + margin-top: 20px; + font-size: 16px; + color: #788498; + } + } .container { background: #FFFFFF; box-shadow: 0px 0px 6px 1px rgba(7,70,165,0.08); @@ -364,6 +379,7 @@ color: #111010; } .item-more { + margin-left: 5px; font-weight: 400; font-size: 12px; color: #525151; diff --git a/src/forge/Server/quality/index.jsx b/src/forge/Server/quality/index.jsx index 5549b0c03..26b57520a 100644 --- a/src/forge/Server/quality/index.jsx +++ b/src/forge/Server/quality/index.jsx @@ -10,6 +10,7 @@ import labelIcon from '../img/quality/label.png'; import code_lines from '../img/quality/code_lines.png'; import repeat from '../img/quality/repeat.png'; import cover from '../img/quality/cover.png'; +import nodata from '../../Images/nodata.png' import { Link } from "react-router-dom"; @@ -273,7 +274,7 @@ function IssueList(props) { {renderFilterItems(listFilter.tags, selectedTag, setSelectedTag, "标签")}
    - {issues && Object.entries(issues).map(([key, item]) => ( + {issues && baseInfo.total > 0 && Object.entries(issues).map(([key, item]) => (
    {key.split(':')[1]} @@ -306,7 +307,11 @@ function IssueList(props) { ))}
    ))} - setPageNum(e)} onShowSizeChange={(current, size) => setPageSize(size)} /> + { baseInfo.total > 0 && setPageNum(e)} onShowSizeChange={(current, size) => setPageSize(size)} /> } + {!baseInfo.total &&
    + +

    暂无问题

    +
    }
    From a12939e9d981b33c4423052a2a7eb750334ab9b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=BF=83=E5=AE=87?= Date: Thu, 20 Jun 2024 09:04:52 +0800 Subject: [PATCH 8/8] =?UTF-8?q?sonar=E4=BC=98=E5=8C=96=E3=80=81bug?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/util/ShareUtil.js | 2 +- src/forge/Server/Index.jsx | 6 +-- src/forge/Server/List.jsx | 16 +++--- .../quality/component/ProblemDetail.jsx | 2 +- src/forge/Server/quality/detail.scss | 15 ++++++ src/forge/Server/quality/index.jsx | 54 ++++++++++++++----- 6 files changed, 68 insertions(+), 27 deletions(-) diff --git a/src/common/util/ShareUtil.js b/src/common/util/ShareUtil.js index 3eb42e755..2778b8d2d 100644 --- a/src/common/util/ShareUtil.js +++ b/src/common/util/ShareUtil.js @@ -3,7 +3,7 @@ const host = window.location.protocol + '//' + window.location.host const wx = window.wx const appId = 'wxf8debe756b4a0309' -let settings = localStorage.chromesetting&&JSON.parse(localStorage.chromesetting); +let settings = localStorage.chromesetting && localStorage.chromesetting !== 'undefined' && JSON.parse(localStorage.chromesetting); let actionUrl = settings && settings.common.zone +'/api'; const service = axios.create({ diff --git a/src/forge/Server/Index.jsx b/src/forge/Server/Index.jsx index 6712c499e..9b47adeae 100644 --- a/src/forge/Server/Index.jsx +++ b/src/forge/Server/Index.jsx @@ -40,9 +40,9 @@ function ServerIndex(props){ useEffect(()=>{ // 非项目协作者不可进入服务tab (3397) - // if(projectDetail && projectDetail.permission === ""){ - // props.history.push(`/${owner}/${projectsId}`); - // } + if(projectDetail && projectDetail.permission === ""){ + props.history.push(`/${owner}/${projectsId}`); + } }, [projectDetail]) return( diff --git a/src/forge/Server/List.jsx b/src/forge/Server/List.jsx index 23e6ddf54..9e535d07c 100644 --- a/src/forge/Server/List.jsx +++ b/src/forge/Server/List.jsx @@ -17,13 +17,13 @@ function Main(props){ } }, [projectDetail]) - // useEffect(()=>{ - // if(current_user && !current_user.login){ - // props.history.push(`/login?go_page=/${owner}/${projectsId}/service`); - // }else{ - // setHas_trace_user(current_user.has_trace_user); - // } - // },[current_user]) + useEffect(()=>{ + if(current_user && !current_user.login){ + props.history.push(`/login?go_page=/${owner}/${projectsId}/service`); + }else{ + setHas_trace_user(current_user.has_trace_user); + } + },[current_user]) function onOk(){ setVisible(false); @@ -66,7 +66,7 @@ function Main(props){
  • - + 代码质量分析工具

    提供自动化的静态代码检查能力,能够识别代码中的缺陷、安全漏洞、代码异味,同时产出代码覆盖率和重复度的详细报告

    diff --git a/src/forge/Server/quality/component/ProblemDetail.jsx b/src/forge/Server/quality/component/ProblemDetail.jsx index ca33e79f3..bbca4f733 100644 --- a/src/forge/Server/quality/component/ProblemDetail.jsx +++ b/src/forge/Server/quality/component/ProblemDetail.jsx @@ -30,7 +30,7 @@ function ProblemDetail({visible, issue, issueDetail, onClose}){ { issueDetail.type } { issueDetail.severity } 生效时间 { moment(issue.creationDate).format('YYYY年MM月DD日') } - { issueDetail.lang } + { issueDetail.langName }

    diff --git a/src/forge/Server/quality/detail.scss b/src/forge/Server/quality/detail.scss index 84a424690..8a4a10dc8 100644 --- a/src/forge/Server/quality/detail.scss +++ b/src/forge/Server/quality/detail.scss @@ -245,9 +245,17 @@ } .issue-list { display: flex; + .show-more-list { + max-height: 2300px; + } + .show-less-list { + max-height: 1400px; + } .left-content { width: 218px; flex-shrink: 0; + transition: max-height 0.4s; + overflow: hidden; .clear-button { margin: 17px 0 0 19px; padding: 0 7px; @@ -300,6 +308,13 @@ width: auto; min-width: unset; } + .show-more { + cursor: pointer; + font-size: 14px; + color: #5D6790; + margin-left: 20px; + margin-top: 4px; + } } } .right-content { diff --git a/src/forge/Server/quality/index.jsx b/src/forge/Server/quality/index.jsx index 26b57520a..1b9c69efb 100644 --- a/src/forge/Server/quality/index.jsx +++ b/src/forge/Server/quality/index.jsx @@ -37,7 +37,9 @@ function IssueList(props) { const [pageSize, setPageSize] = useState(10); const [loading, setLoading] = useState(false); const [pageLoading, setPageLoading] = useState(true); - const [analysisStatus, setAnalysisStatus] = useState(status.none); + const [analysisStatus, setAnalysisStatus] = useState(null); + const [tagShowMore, setTagShowMore] = useState(false); + const [tagShowMoreDelay, setTagShowMoreDelay] = useState(false); const { projectDetail, history } = props; const metricKeys = 'lines,coverage,lines_to_cover,duplicated_lines_density,maintainability_issues,vulnerabilities,bugs,code_smells'; const facets = 'impactSeverities,tags,types'; @@ -130,6 +132,16 @@ function IssueList(props) { } }, [pageNum, pageSize]); + useEffect(() => { + if (!tagShowMore) { + setTimeout(() => { + setTagShowMoreDelay(tagShowMore) + }, 450); + } else { + setTagShowMoreDelay(tagShowMore) + } + }, [tagShowMore]) + const detail = (e) => { setSelectedIssue(e); getProblemDetail(e.rule, owner, projectsId).then(res => { @@ -139,6 +151,7 @@ function IssueList(props) { }; const toggleSelection = (state, setter, value) => { + window.scrollTo(0,0); const index = state.indexOf(value); if (index > -1) { state.splice(index, 1); @@ -148,13 +161,22 @@ function IssueList(props) { } }; - const renderFilterItems = (items, state, setter, label) => ( -
    + const clearAllSelection = () => { + setSelectedType([]) + setSelectedSeverity([]) + setSelectedTag([]) + } + + // 筛选列表 + const renderFilterItems = (items, state, setter, label, showMoreButton = false) => { + const displayedItems = showMoreButton && items && !tagShowMoreDelay ? items.slice(0, 10) : items; + + return (
    {label} { state.length > 0 && }
    - {items && items.map(e => ( + {displayedItems && displayedItems.map(e => (
    {e.count}
    ))} + { showMoreButton && items.length > 10 && { setTagShowMore(!tagShowMore) }}> { tagShowMore ? '收起' : '显示更多'} } -
    - ); +
    ) + }; + // 总览项 const renderTotalItem = (label, value, icon) => (
    {label} @@ -175,6 +199,7 @@ function IssueList(props) {
    ); + // 总览项(带代码行数) const renderDetailedItem = (label, value, percent, moreValue, icon) => (
    {label} @@ -212,6 +237,7 @@ function IssueList(props) { }) } + // 开始按钮状态 const getStatusButton = () => { switch (analysisStatus) { case actionStatus.none: @@ -221,11 +247,11 @@ function IssueList(props) { return case actionStatus.failure: return
    -

    代码正在分析中,请重试

    +

    分析失败,请重试

    default: - return + return <> } } @@ -260,18 +286,18 @@ function IssueList(props) { {renderTotalItem("Bugs", baseData.bugs, issueTypeIcon.BUG)} {renderTotalItem("漏洞", formatNumber(baseData.vulnerabilities), issueTypeIcon.VULNERABILITY)} {renderTotalItem("异味", formatNumber(baseData.code_smells), issueTypeIcon.CODE_SMELL)} - {renderDetailedItem("重复率", baseData.lines, baseData.duplicated_lines_density, "代码", repeat)} - {renderDetailedItem("覆盖率", baseData.lines_to_cover, baseData.coverage, "代码", cover)} - {renderTotalItem("代码行数", formatNumberWithCommas(baseData.lines), code_lines)} + {renderDetailedItem("重复率", baseData.lines || '-', baseData.duplicated_lines_density || '-', "代码", repeat)} + {renderDetailedItem("覆盖率", baseData.lines_to_cover || '-', baseData.coverage || '-', "代码", cover)} + {renderTotalItem("代码行数", formatNumberWithCommas(baseData.lines || '-'), code_lines)}
  • {issues &&
    发现{ baseData.maintainability_issues && JSON.parse(baseData.maintainability_issues).total }个问题
    }
    -
    - { (selectedType.length > 0 || selectedSeverity.length > 0 || selectedTag .length > 0) && } +
    + { (selectedType.length > 0 || selectedSeverity.length > 0 || selectedTag .length > 0) && } {renderFilterItems(listFilter.types, selectedType, setSelectedType, "问题类型")} {renderFilterItems(listFilter.impactSeverities, selectedSeverity, setSelectedSeverity, "严重程度")} - {renderFilterItems(listFilter.tags, selectedTag, setSelectedTag, "标签")} + {renderFilterItems(listFilter.tags, selectedTag, setSelectedTag, "标签", true)}
    {issues && baseInfo.total > 0 && Object.entries(issues).map(([key, item]) => (