diff --git a/package.json b/package.json index 70aa4b155..04b3ddf92 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,7 @@ "react-router-dom": "^4.2.2", "react-slick": "^0.28.1", "react-split-pane": "^0.1.91", + "react-svg": "^16.3.0", "react-url-query": "^1.5.0", "react-zmage": "^0.8.5-beta.31", "redux": "^4.0.5", diff --git a/src/AppConfig.js b/src/AppConfig.js index ace66200a..2a225d262 100644 --- a/src/AppConfig.js +++ b/src/AppConfig.js @@ -39,7 +39,7 @@ export function initAxiosInterceptors(props) { return config } requestProxy(config); - let url = `/api${config.url}`; + let url = config.url.startsWith('/api') ? config.url : `/api${config.url}`; // if (`${config[0]}` !== `true`) { // if (window.location.port === "3007") { diff --git a/src/forge/Main/CoderDepot.jsx b/src/forge/Main/CoderDepot.jsx index a7e6ac239..61cc23106 100644 --- a/src/forge/Main/CoderDepot.jsx +++ b/src/forge/Main/CoderDepot.jsx @@ -242,7 +242,11 @@ function CoderDepot(props){ let ele = document.getElementById("ptxt"); if(ele){ let h = ele.offsetHeight; - if( h <= 36 ) setHideBtn(false); + if( h <= 36 ) { + setHideBtn(false); + } else { + setHideBtn(true) + } } } },[projectDetail,lastCommit]) @@ -589,7 +593,7 @@ function CoderDepot(props){ { lastCommit.message &&
-
+

{lastCommit.message}

} { hideBtn && __CLIENT__ &&
changeHide(hide)}>
} diff --git a/src/forge/Server/Index.jsx b/src/forge/Server/Index.jsx index 0e18af571..09f6f162d 100644 --- a/src/forge/Server/Index.jsx +++ b/src/forge/Server/Index.jsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { Route, Switch } from 'react-router-dom'; import "./index.scss"; import Loadable from 'react-loadable'; @@ -46,6 +46,7 @@ const MeasurementDetail = Loadable({ function ServerIndex(props){ const { projectDetail } = props; const { owner , projectsId } = props.match.params; + const [ qualityClass, setQualityClass ] = useState(false) useEffect(()=>{ // 非项目协作者不可进入服务tab (3397) @@ -54,8 +55,12 @@ function ServerIndex(props){ } }, [projectDetail]) + const changeClass = (isServer = false ) => { + setQualityClass(isServer); + } + return( -
+
() + () => () } > () + () => () } > {/* 个人建站服务 */} diff --git a/src/forge/Server/List.jsx b/src/forge/Server/List.jsx index 57f51ca95..383ca9dda 100644 --- a/src/forge/Server/List.jsx +++ b/src/forge/Server/List.jsx @@ -70,7 +70,7 @@ function Main(props){ */}
  • - + 代码质量分析工具

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

    diff --git a/src/forge/Server/api.js b/src/forge/Server/api.js index acf0679af..2c894ea76 100644 --- a/src/forge/Server/api.js +++ b/src/forge/Server/api.js @@ -67,3 +67,19 @@ export function getActionList(owner, repo ) { }); } +// 获取classes、files地址 +export function getAnalizeUrl(owner, repo ) { + return fetch({ + url: `/api/v1/${owner}/${repo}/sonarqubes/doxygen_url`, + method: 'get', + }); +} + +// 获取方法调用分析 +export function getFuncAnalize(owner, repo ) { + return fetch({ + url: `/api/v1/${owner}/${repo}/sonarqubes/analyze_doxygen`, + method: 'get', + }); +} + diff --git a/src/forge/Server/constants/quality.js b/src/forge/Server/constants/quality.js index c44461377..fa150cc13 100644 --- a/src/forge/Server/constants/quality.js +++ b/src/forge/Server/constants/quality.js @@ -1,9 +1,16 @@ -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' +import bugs from '../img/quality/bugs.svg' +import vulnerabilities from '../img/quality/vulnerabilities.svg' +import code_smells from '../img/quality/code_smells.svg' +import blocked from '../img/quality/blocked.svg' +import low from '../img/quality/low.svg' +import medium from '../img/quality/medium.svg' +import high from '../img/quality/high.svg' +import label1 from '../img/quality/label1.svg' +import label2 from '../img/quality/label2.svg' +import label3 from '../img/quality/label3.svg' +import label4 from '../img/quality/label4.svg' +import label5 from '../img/quality/label5.svg' + export const issueType = { BUG: 'BUG', @@ -17,9 +24,10 @@ export const issueTypeKeys = { CODE_SMELL: 'code_smells' } export const maintainability = { - LOW: '低', - MEDIUM: '中', - HIGH: '高' + LOW: '次要', + MEDIUM: '主要', + HIGH: '严重', + BLOCKED: '阻塞' } export const issueTypeIcon = { @@ -31,5 +39,14 @@ export const issueTypeIcon = { export const maintainabilityIcon = { LOW: low, MEDIUM: medium, - HIGH: high + HIGH: high, + BLOCKED: blocked +} + +export const labelIcon = { + 0: label1, + 1: label2, + 2: label3, + 3: label4, + 4: label5 } diff --git a/src/forge/Server/img/logoq.png b/src/forge/Server/img/logoq.png new file mode 100644 index 000000000..a2415461f Binary files /dev/null and b/src/forge/Server/img/logoq.png differ diff --git a/src/forge/Server/img/quality/blocked.svg b/src/forge/Server/img/quality/blocked.svg new file mode 100644 index 000000000..2b5760fd5 --- /dev/null +++ b/src/forge/Server/img/quality/blocked.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/img/quality/bugs.svg b/src/forge/Server/img/quality/bugs.svg new file mode 100644 index 000000000..4d532fda8 --- /dev/null +++ b/src/forge/Server/img/quality/bugs.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/img/quality/class.svg b/src/forge/Server/img/quality/class.svg new file mode 100644 index 000000000..def459276 --- /dev/null +++ b/src/forge/Server/img/quality/class.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/forge/Server/img/quality/code_lines.svg b/src/forge/Server/img/quality/code_lines.svg new file mode 100644 index 000000000..4fdcd1cbb --- /dev/null +++ b/src/forge/Server/img/quality/code_lines.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/img/quality/code_smells.svg b/src/forge/Server/img/quality/code_smells.svg new file mode 100644 index 000000000..39a9e20ca --- /dev/null +++ b/src/forge/Server/img/quality/code_smells.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/img/quality/cover.svg b/src/forge/Server/img/quality/cover.svg new file mode 100644 index 000000000..c0739f10c --- /dev/null +++ b/src/forge/Server/img/quality/cover.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/img/quality/detail-bg.png b/src/forge/Server/img/quality/detail-bg.png new file mode 100644 index 000000000..35507dd77 Binary files /dev/null and b/src/forge/Server/img/quality/detail-bg.png differ diff --git a/src/forge/Server/img/quality/files.svg b/src/forge/Server/img/quality/files.svg new file mode 100644 index 000000000..7613eb9f4 --- /dev/null +++ b/src/forge/Server/img/quality/files.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/forge/Server/img/quality/funcs.svg b/src/forge/Server/img/quality/funcs.svg new file mode 100644 index 000000000..29683e4c5 --- /dev/null +++ b/src/forge/Server/img/quality/funcs.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/img/quality/high.svg b/src/forge/Server/img/quality/high.svg new file mode 100644 index 000000000..f3927f3f5 --- /dev/null +++ b/src/forge/Server/img/quality/high.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/forge/Server/img/quality/issue-bg.png b/src/forge/Server/img/quality/issue-bg.png new file mode 100644 index 000000000..c9e558c4c Binary files /dev/null and b/src/forge/Server/img/quality/issue-bg.png differ diff --git a/src/forge/Server/img/quality/label1.svg b/src/forge/Server/img/quality/label1.svg new file mode 100644 index 000000000..d3014c9dd --- /dev/null +++ b/src/forge/Server/img/quality/label1.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/img/quality/label2.svg b/src/forge/Server/img/quality/label2.svg new file mode 100644 index 000000000..3dea807f3 --- /dev/null +++ b/src/forge/Server/img/quality/label2.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/img/quality/label3.svg b/src/forge/Server/img/quality/label3.svg new file mode 100644 index 000000000..a13b12a4e --- /dev/null +++ b/src/forge/Server/img/quality/label3.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/img/quality/label4.svg b/src/forge/Server/img/quality/label4.svg new file mode 100644 index 000000000..260475e05 --- /dev/null +++ b/src/forge/Server/img/quality/label4.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/forge/Server/img/quality/label5.svg b/src/forge/Server/img/quality/label5.svg new file mode 100644 index 000000000..2db9288df --- /dev/null +++ b/src/forge/Server/img/quality/label5.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/forge/Server/img/quality/low.svg b/src/forge/Server/img/quality/low.svg new file mode 100644 index 000000000..862844d56 --- /dev/null +++ b/src/forge/Server/img/quality/low.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/img/quality/medium.svg b/src/forge/Server/img/quality/medium.svg new file mode 100644 index 000000000..664b8c77d --- /dev/null +++ b/src/forge/Server/img/quality/medium.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/img/quality/page-bg.png b/src/forge/Server/img/quality/page-bg.png new file mode 100644 index 000000000..6c72fb864 Binary files /dev/null and b/src/forge/Server/img/quality/page-bg.png differ diff --git a/src/forge/Server/img/quality/repeat.svg b/src/forge/Server/img/quality/repeat.svg new file mode 100644 index 000000000..1d9bf2232 --- /dev/null +++ b/src/forge/Server/img/quality/repeat.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/img/quality/right-arrow.svg b/src/forge/Server/img/quality/right-arrow.svg new file mode 100644 index 000000000..9aab70a31 --- /dev/null +++ b/src/forge/Server/img/quality/right-arrow.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/img/quality/right-list-bg.png b/src/forge/Server/img/quality/right-list-bg.png new file mode 100644 index 000000000..1c93e3a20 Binary files /dev/null and b/src/forge/Server/img/quality/right-list-bg.png differ diff --git a/src/forge/Server/img/quality/total-bg.png b/src/forge/Server/img/quality/total-bg.png new file mode 100644 index 000000000..53c041be5 Binary files /dev/null and b/src/forge/Server/img/quality/total-bg.png differ diff --git a/src/forge/Server/img/quality/triangular-arrow.png b/src/forge/Server/img/quality/triangular-arrow.png new file mode 100644 index 000000000..12d4a1c7b Binary files /dev/null and b/src/forge/Server/img/quality/triangular-arrow.png differ diff --git a/src/forge/Server/img/quality/vulnerabilities.svg b/src/forge/Server/img/quality/vulnerabilities.svg new file mode 100644 index 000000000..713a47a68 --- /dev/null +++ b/src/forge/Server/img/quality/vulnerabilities.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/forge/Server/index.scss b/src/forge/Server/index.scss index 4b8b2018b..ac7e607e1 100644 --- a/src/forge/Server/index.scss +++ b/src/forge/Server/index.scss @@ -2,6 +2,15 @@ width: 1200px; margin:20px auto; } +.panels-quality { + background:#f3f5f9; + background-image: url("./img/quality/page-bg.png"); + background-repeat: no-repeat; + background-size: 100%; + box-sizing: content-box; + width: 100%; + margin: 0; +} .btnhover{ background-color: #466aff; color: #fff!important; diff --git a/src/forge/Server/quality/component/CallTable.jsx b/src/forge/Server/quality/component/CallTable.jsx new file mode 100644 index 000000000..183ab12bb --- /dev/null +++ b/src/forge/Server/quality/component/CallTable.jsx @@ -0,0 +1,65 @@ +import React, { useEffect, useState } from 'react'; +import { Table, Spin } from 'antd'; +import './index.scss' +import { getFuncAnalize } from '../../api'; + +function CallTable(props){ + const [ loading, setLoading ] = useState(false); + const [ listData, setListData ] = useState([]) + const { owner , projectsId } = props.match.params; + + + useEffect(() => { + getlist() + },[]) + + const getlist = () => { + setLoading(true) + getFuncAnalize(owner, projectsId).then(res => { + if (res.status === 0) { + setListData(res.data) + } + setLoading(false) + }) + } + + const openCode = (text) => { + window.open(`${props.match.url}/tree/${ props.projectDetail.default_branch }/${text.split(':')[0]}`) + } + + const columns = [ + { + title: "方法名", + dataIndex: "function", + }, + { + title: "方法位置", + dataIndex: "definition", + render: (text, item) => ( + text !== 'unknown' ? openCode(text)} className="color-blue pointer"> + {text} + : text + ), + }, + { + title: "调用方法数", + dataIndex: "call_count", + }, + { + title: "被调用次数", + dataIndex: "called_count", + }, + ]; + + return +
    + + +
    +
    +
    +} +export default CallTable; diff --git a/src/forge/Server/quality/component/index.scss b/src/forge/Server/quality/component/index.scss index 6170527c7..016b1ac57 100644 --- a/src/forge/Server/quality/component/index.scss +++ b/src/forge/Server/quality/component/index.scss @@ -41,4 +41,15 @@ margin-left: 70px; } } +} +.call-table { + background: #f9fafd; + background-image: url("../../img/quality/right-list-bg.png"); + background-repeat: no-repeat; + background-size: 100%; + border: 1px solid; + border-color: #eff0f1; + border-radius: 7px; + padding: 20px; + margin-bottom: 20px; } \ No newline at end of file diff --git a/src/forge/Server/quality/detail.jsx b/src/forge/Server/quality/detail.jsx index 5e9f26fdd..0031b9089 100644 --- a/src/forge/Server/quality/detail.jsx +++ b/src/forge/Server/quality/detail.jsx @@ -4,8 +4,8 @@ import './detail.scss'; import ProblemDetail from "./component/ProblemDetail"; import { issueType, issueTypeIcon } from "../constants/quality" import folderImg from '../img/quality/folder.png' -import backImg from '../img/quality/back.png' -import { Divider } from 'antd'; +import { Button } from 'antd'; +import arrow from '../img/quality/triangular-arrow.png' function issueDetail(props){ const { owner , projectsId } = props.match.params; @@ -63,7 +63,7 @@ function issueDetail(props){ let newIssues = data.issues let issuesHandled = JSON.parse(JSON.stringify(issues)) setTotal(data.total) - newIssues.forEach(e => { + newIssues && newIssues.forEach(e => { if (issuesHandled[e.component]) { issuesHandled[e.component].push(e) } else { @@ -166,64 +166,80 @@ function issueDetail(props){ }) } - return
    - setDetailVisible(false)}> -
    - history.goBack()}/> - - { - issues && Object.entries(issues).map(([key, item]) =>
    -
    { key.split(`:`)[1] }
    - { - item.map(e => ( -
    setSelectedIssue(e)}> -

    { e.message }

    -

    { 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) }> - { index } - { flow.locations[0].msg } -
    - }) - } -
    - )) - } -
    - ) - } - { pageNum * pageSize < total &&
    setPageNum(pageNum + 1)}>查看更多
    } -
    + return
    { - snippetsDetail &&
    -
    { snippetsDetail.component.path }
    -
    - - {snippetsDetail.sources.map((item, index) => ( - <> - {item.line} - -
    -
     selectRepeat(e, item.line)}/>
    -                    {issues[selectedIssue.component] && issues[selectedIssue.component].map(e => {
    -                      if (e.line === item.line) {
    -                        return 
    setSelectedIssue(e)}> - {e.message} - detail()}>问题分析 -
    ; - } - })} -
    - - - { snippetsDetail.sources[index + 1] && (snippetsDetail.sources[index + 1].line - item.line > 1) && ......} - - ))} - -
    +
    + 代码质量静态扫描 + 护航研发效能
    } +
    +
    + setDetailVisible(false)}> +
    +

    代码分析结果,全方位精准定位问题

    +

    深度剖析代码细节,精准定位潜在问题,助力团队提升开发效率,打造高质量软件,为项目稳定运行保驾护航,开启高效开发新时代。

    + + +
    +
    +
    + { + issues && Object.entries(issues).map(([key, item]) =>
    +
    { key.split(`:`)[1] }
    + { + item.map(e => ( +
    setSelectedIssue(e)}> +

    { e.message }

    +

    { 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) }> + { index } + { flow.locations[0].msg } +
    + }) + } +
    + )) + } +
    + ) + } + { pageNum * pageSize < total &&
    setPageNum(pageNum + 1)}>查看更多
    } +
    + { + snippetsDetail &&
    +
    { snippetsDetail.component.path }
    +
    + + {snippetsDetail.sources.map((item, index) => ( + <> + {item.line} + +
    +
     selectRepeat(e, item.line)}/>
    +                          {issues[selectedIssue.component] && issues[selectedIssue.component].map(e => {
    +                            if (e.line === item.line) {
    +                              return 
    setSelectedIssue(e)}> + {e.message} + detail()}>问题分析 +
    ; + } + })} +
    + + + { snippetsDetail.sources[index + 1] && (snippetsDetail.sources[index + 1].line - item.line > 1) && ......} + + ))} + +
    +
    + } +
    +
    +
    } 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 6df2331ba..06f232240 100644 --- a/src/forge/Server/quality/detail.scss +++ b/src/forge/Server/quality/detail.scss @@ -1,13 +1,70 @@ +.page-quality { + width: 1200px; + margin: 0 auto; + padding: 20px 0; + .container { + border-radius: 10px; + box-shadow: 0px 0px 6px rgba(7, 70, 165, 0.08); + background: #FFFFFF; + } +} + .bug-details { - display: flex; - max-height: 80vh; + max-height: 90vh; + width: 1200px; + background: #ffffff; + padding: 25px 30px; + .details-wrapper { + background: #f6f8ff; + background-image: url("../img/quality/detail-bg.png"); + background-repeat: no-repeat; + background-size: 100%; + border: 1px solid; + border-color: #eff0f1; + border-radius: 7px; + width: 100%; + padding: 30px 25px; + .details-header { + position: relative; + p { + color: #0c296e; + font-size: 14px; + max-width: 850px; + line-height:normal; + } + p:first-child { + font-weight: 500; + font-size: 16px; + padding-bottom: 10px; + } + .triangular-arrow { + position: absolute; + left: 260px; + top: -5px; + width: 36px; + height: 44px; + } + .back-btn { + width: 79px; + height: 32px; + background: #466aff; + border-radius: 6px; + position: absolute; + right: 0; + top: 0; + } + } + .details-body { + display: flex; + margin-top: 30px; + max-height: calc(90vh - 175px ); + } + } .left-list { overflow-y: auto; width: 350px; flex-shrink: 0; - .back-icon { - width: 24px; - } + max-height: 100%; .ant-divider { padding-right: 10px; width: 330px; @@ -46,18 +103,20 @@ } .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; word-break: break-word; - font-family: PingFang SC, PingFang SC; font-size: 14px; color: #111010; cursor: pointer; + background: #ffffff; + border: 1px solid; + border-color: #e5ecf8; + border-radius: 6px; &:hover { - border: 1px solid #466AFF; + background-image: url('../img/quality/issue-bg.png'); + background-repeat: no-repeat; + background-size: 100% 100%; } .issue-typeline { display: flex; @@ -74,10 +133,12 @@ .issue-duplication { margin-left: 5px; padding: 0 8px; - background: #57575B; - border-radius: 4px 4px 4px 4px; - font-weight: 400; - color: #FFFFFF; + color: #042182; + font-size: 14px; + height: 20px; + background:#e6f0ff; + border-radius: 4px; + line-height: 20px; } .repeat-item { span { @@ -106,15 +167,20 @@ background: #D4333F; } span:nth-of-type(2) { - background: #475760; + background: #274faf; + color: #FFFFFF; } } } .selected-issue { background: #FFFFFF; - border: 1px solid #2A61FF; + background-image: url('../img/quality/issue-bg.png'); + background-repeat: no-repeat; + background-size: 100% 100%; + border: 2px solid #2A61FF; .issue-duplication { background: #D4333F; + color: #FFFFFF; } } } @@ -133,6 +199,7 @@ line-height: 42px; display: flex; align-items: center; + border-radius:6px 6px 0px 0px; img { width: 18px; margin-right: 10px; @@ -147,6 +214,8 @@ .content { display: flex; border: 1px solid #ECEEEF; + max-height: calc(90vh - 217px); + overflow-y: auto; tbody { width: 100%; } @@ -221,7 +290,24 @@ } } } +.quality-title { + font-family: 'alibaba'; + font-weight: 500; + color: #191919; + font-size: 36px; + text-align: center; + margin-bottom: 38px; + line-height: 1; + span:nth-child(1) { + background-image: linear-gradient(92.79deg,#2569f2 5.43%,#40bcfd 96.17%); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + margin-right: 18px; + } +} .issue-total { + font-family: 'alibabaReg'; .nodata { height: 100%; display: flex; @@ -237,20 +323,12 @@ color: #788498; } } - .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; - } - .count-title { - font-weight: bold; - font-size: 20px; - color: #333333; - margin: 28px 0; - } .issue-list { display: flex; + background: #ffffff; + border-radius: 0px 10px 10px 0px; + margin-top: 30px; + padding: 27px 23px; .show-more-list { max-height: 2300px; } @@ -263,23 +341,37 @@ transition: max-height 0.4s; overflow: hidden; .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; + height: 23px; cursor: pointer; + background: #ffffff; + border: 1px solid; + border-color: #d4333f; + border-radius: 3px; + color: #d4333f; + font-size: 13px; + line-height: 23px; + } + .count { + display: flex; + align-items: center; + justify-content: space-between; + .count-title { + font-family: 'alibaba'; + color: #191919; + font-size: 16px; + margin-right: 33px; + } } .filters { .filter-title { - font-weight: bold; - font-size: 17px; - color: #000000; - padding: 20px; + color: #9a99b9; + font-size: 13px; + padding: 17px 0 17px 7px; line-height: 28px; + display: flex; + align-items: center; + justify-content: space-between; .clear-button { float: right; margin: 0; @@ -293,20 +385,32 @@ align-items: center; height: 50px; cursor: pointer; + color:#403e63; + font-size: 15px; &:hover { - background: #466bff0c; + background:#f6f8ff; + color:#466aff; + border-radius:5px; } .item-type { display: flex; align-items: center; - img { - width: 16px; + .type-icon { margin-right: 5px; + transform: scale(1.125); + div { + height: 18px; + display: flex; + align-items: center; + } } } } .selected-item { - background: #466bff17; + background:#f6f8ff; + color:#466aff; + border-radius:5px; + filter: drop-shadow(#466aff 1000px 0) } .ant-divider { margin: 20px; @@ -326,20 +430,48 @@ .right-content { margin-left: 17px; flex-grow: 1; - padding: 20px; padding-top: 10px; + .content-warap { + background: #f9fafd; + border: 1px solid; + border-color: #eff0f1; + border-radius: 7px; + padding: 0 20px 20px 20px; + margin-bottom: 20px; + } + .has-issues { + background-image: url("../img/quality/right-list-bg.png"); + background-repeat: no-repeat; + background-size: 100%; + } .doc-title { - font-weight: bold; font-size: 16px; - color: #111010; - margin: 19px 0; + margin: 30px 0 15px 0; + width: 509px; + color: #191919; + img { + width: 18px; + margin-right: 10px; + } } .issue { padding: 15px 20px; - background: #FAFCFF; - border-radius: 3px 3px 3px 3px; - border: 1px solid rgba(42,97,255,0.23); margin-bottom: 12px; + background: #ffffff; + border: 1px solid; + border-color: rgba(123, 134, 180, 0.11); + border-radius: 6px; + box-shadow: 0px 0px 8px rgba(70, 106, 255, 0.05); + border: 2px solid; + border-color: transparent; + cursor: pointer; + &:hover { + border: 2px solid; + border-color: #466aff; + .issue-message { + color: #466aff; + } + } .content-info { font-weight: 400; font-size: 14px; @@ -356,74 +488,136 @@ } span:nth-of-type(n + 4) { margin-left: auto; + img { + color: #5D6790; + } + } + .icon-text { + display: flex; + align-items: center; + img{ + width: 16px; + margin-right: 5px; + } + .svg-icon { + margin-right: 5px; + div { + display: flex; + align-items: center; + } + } } } .issue-message { - font-weight: bold; font-size: 15px; - color: #111010; + color: #000000; word-break: break-word; + display: flex; span:nth-of-type(1) { font-weight: 400; font-size: 14px; color: #466AFF; - margin-left: 16px; + margin-left: auto; cursor: pointer; } span:nth-of-type(2) { float: right; + font-family: 'alibaba'; + color: #000000; + &::before { + content: "|"; + color: rgba(187,189,197,0.61); + margin-right: 13px; + margin-left: 13px; + } } } } + .ant-pagination { + float: right; + } } } .total-info { padding: 20px; + .sub-container { + background-color: #f6f8ff; + background-image: url("../img/quality/total-bg.png"); + background-repeat: no-repeat; + background-size: 100% 100%; + padding: 21px 25px; + position: relative; + } p { - font-weight: bold; - font-size: 18px; - color: #333333; + color: #0c296e; + font-size: 14px; + max-width: 565px; + line-height:normal; + } + p:first-child { + font-family: 'alibaba'; + font-weight: 500; + font-size: 16px; + padding-bottom: 10px; + } + .triangular-arrow { + position: absolute; + left: 580px; + top: 40px; + width: 48px; + height: 58px; } .info-list { display: flex; + flex-wrap: wrap; + width: 800px; .total-item { + width: 250px; + height: 50px; + background: #ffffff; + border-radius: 5px; + box-shadow: 0px 0px 6px rgba(33, 73, 212, 0.06); display: flex; - flex-direction: column; - min-height: 53px; - font-weight: 400; + align-items: center; + justify-content: space-between; font-size: 15px; - color: #525151; - margin-top: 18px; - > span:nth-child(2) { - font-weight: bold; - font-size: 18px; - color: #111010; - } - .item-more { - margin-left: 5px; - font-weight: 400; - font-size: 12px; - color: #525151; - span { - color: #466AFF; + color: #0c296e; + margin-top: 15px; + margin-right: 15px; + padding: 12px 15px; + .item-text { + display: flex; + align-items: center; + img { + margin-right: 4px; } } - } - .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: 100%; - border-left: 1px solid rgba(187,189,197,0.61); + .item-value { + font-family: YouSheBiaoTiHei; + color: #182952; + font-size: 20px; + } + .item-more { + font-weight: 400; + font-size: 12px; + margin-right: auto; } } + // .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: 100%; + // border-left: 1px solid rgba(187,189,197,0.61); + // } + // } } } } @@ -456,12 +650,8 @@ border-bottom: 1px solid rgba(90, 117, 193, 0.23); } } - -.icon-text { - display: flex; - align-items: center; - img { - width: 16px; - margin-right: 5px; - } +.iframe { + border: 1px solid; + border-color: #eff0f1; + border-radius: 7px; } \ No newline at end of file diff --git a/src/forge/Server/quality/index.jsx b/src/forge/Server/quality/index.jsx index 573f7a442..24a77b6fb 100644 --- a/src/forge/Server/quality/index.jsx +++ b/src/forge/Server/quality/index.jsx @@ -1,18 +1,23 @@ -import React, { useState, useEffect, Fragment } from "react"; -import { getList, getProblemDetail, getProblemData, initSonar, startAlalyze, getActionList } from "../api"; +import React, { useState, useEffect, Fragment, useRef } from "react"; +import { getList, getProblemDetail, getProblemData, initSonar, startAlalyze, getActionList, getAnalizeUrl } from "../api"; import './detail.scss'; import ProblemDetail from "./component/ProblemDetail"; -import { issueType, maintainability, issueTypeIcon, maintainabilityIcon } from "../constants/quality"; +import { issueType, maintainability, issueTypeIcon, maintainabilityIcon, labelIcon } from "../constants/quality"; 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'; -import code_lines from '../img/quality/code_lines.png'; -import repeat from '../img/quality/repeat.png'; -import cover from '../img/quality/cover.png'; +import code_lines from '../img/quality/code_lines.svg'; +import repeat from '../img/quality/repeat.svg'; +import cover from '../img/quality/cover.svg'; import nodata from '../../Images/nodata.png' -import { Link } from "react-router-dom"; - +import arrow from '../img/quality/triangular-arrow.png' +import files from '../img/quality/files.svg' +import classes from '../img/quality/class.svg' +import funcs from '../img/quality/funcs.svg' +import rightArrow from '../img/quality/right-arrow.svg' +import { ReactSVG } from 'react-svg' +import folderImg from '../img/quality/folder.png' +import CallTable from "./component/CallTable"; const actionStatus = { success: 1, @@ -22,6 +27,18 @@ const actionStatus = { none: 7 } +const codeAnalyze = { + classes: 1, + files: 2, + func: 3 +} + +const analizeIcon = { + [codeAnalyze.classes]: classes, + [codeAnalyze.files]: files, + [codeAnalyze.func]: funcs +} + function IssueList(props) { const [issues, setIssues] = useState({}); const [baseInfo, setBaseInfo] = useState({}); @@ -40,18 +57,14 @@ function IssueList(props) { const [analysisStatus, setAnalysisStatus] = useState(null); const [tagShowMore, setTagShowMore] = useState(false); const [tagShowMoreDelay, setTagShowMoreDelay] = useState(false); - const { projectDetail, history } = props; + const [codeSelected, setCodeSelected] = useState(false); + const [url, setUrl] = useState(null); + const { projectDetail, history, changeClass } = 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; - - useEffect(() => { - let newContainer = document.getElementsByClassName("newContainer")[0]; - // newContainer.style.backgroundColor = '#F7F9FCFF'; - return () => { - newContainer.style.backgroundColor = '#FFFFFF'; - }; - }, []); + const iframeRef = useRef(null); + const timeRef = useRef(); useEffect(() => { if (projectDetail) { @@ -62,25 +75,80 @@ function IssueList(props) { return } setPageLoading(true) - getActionList(owner, projectsId).then(res => { - setPageLoading(false) - if (res.status !== 0) { - if (res.runs.length === 0) { - setAnalysisStatus(actionStatus.none) - } else { - setAnalysisStatus(res.runs[0].status) - if (res.runs[0].status) { - getData() - getIssueList() - } - } - } else { - message.error(res.message) - } - }) + getAction() } }, [projectDetail]); + useEffect(() => { + return () => { + clearTimer() + } + },[]) + + useEffect(() => { + const iframe = iframeRef.current; + if (!iframe || !url) return; // 确保 iframe 和 url 已存在 + + const handleLoad = () => { + try { + const iframeDocument = iframe.contentWindow?.document; + if (iframeDocument) { + const contentHeight = Math.max( + iframeDocument.body.scrollHeight, iframeDocument.body.offsetHeight, + iframeDocument.documentElement.clientHeight, iframeDocument.documentElement.scrollHeight, iframeDocument.documentElement.offsetHeight + ); + iframe.style.height = contentHeight + 'px'; + } + } catch (e) { + iframe.style.height = '770px'; + // 如果实际上是跨域的,这里很可能会触发错误 + console.error("调整同源 iframe 大小出错 (或者它实际是跨域的):", e); + } + }; + + iframe.addEventListener('load', handleLoad); + // 可选: 如果 iframe 内部尺寸变化,重新计算 (更复杂) + + return () => { + iframe.removeEventListener('load', handleLoad); + }; + }, [url]); // 如果 URL 改变,重新绑定监听器 + + const clearTimer = () => { + if (timeRef.current) { + clearTimeout(timeRef.current); + timeRef.current = undefined; // 清空引用 + } + } + + const getAction = () => { + getActionList(owner, projectsId).then(res => { + setPageLoading(false) + if (res.status !== 0) { + if (res.runs.length === 0) { + setAnalysisStatus(actionStatus.none) + } else { + if (res.runs[0].status === actionStatus.success) { + changeClass(true) + } + if ([actionStatus.waiting, actionStatus.running].includes(res.runs[0].status)) { + if (timeRef.current) clearTimer(); + timeRef.current = setTimeout(() => { + getAction() + }, 3000); + } + setAnalysisStatus(res.runs[0].status) + if (res.runs[0].status) { + getData() + getIssueList() + } + } + } else { + message.error(res.message) + } + }) + } + const getData = () => { getProblemData(owner, projectsId, metricKeys).then(res => { if (res.status === 0) { @@ -95,31 +163,49 @@ function IssueList(props) { } const getIssueList = () => { + const isBLOCKED = selectedSeverity.join(',') === 'BLOCKED' //手动添加阻塞,当只选BLOCKED时筛选为空 let params = { facets: facets, types: selectedType.join(','), - impactSeverities: selectedSeverity.join(','), + impactSeverities: selectedSeverity.join(',').replace('BLOCKED', ''), tags: selectedTag.join(','), p: pageNum, ps: pageSize }; - setPageLoading(true) + if (!timeRef.current) { + setPageLoading(true) + } getList(params, owner, projectsId).then(res => { setPageLoading(false) - const issues = res.issues.reduce((acc, issue) => { + const issues = res.issues && !isBLOCKED ? res.issues.reduce((acc, issue) => { if (acc[issue.component]) { acc[issue.component].push(issue); } else { acc[issue.component] = [issue]; } return acc; - }, {}); + }, {}) : []; - const filter = res.facets.reduce((acc, facet) => { + const filter = res.facets ? res.facets.reduce((acc, facet) => { + if (facet.property === 'impactSeverities') { + facet.values.unshift({ + count: 0, + val: "BLOCKED" + }) + } + + if (isBLOCKED && facet.property !== 'impactSeverities') { + if (facet.property === 'tags') facet.values = [] + facet.values.map(e => { + e.count = 0 + }) + } + acc[facet.property] = facet.values; return acc; - }, {}); + }, {}) : []; + if (isBLOCKED) res.total = 0 setListFilter(filter); setBaseInfo(res); setIssues(issues); @@ -159,6 +245,8 @@ function IssueList(props) { }; const toggleSelection = (state, setter, value) => { + setCodeSelected(undefined) + setUrl(undefined) window.scrollTo(0,0); const index = state.indexOf(value); if (index > -1) { @@ -184,17 +272,23 @@ function IssueList(props) { {label} { state.length > 0 && }
    - {displayedItems && displayedItems.map(e => ( + {displayedItems && displayedItems.map((e, index) => (
    toggleSelection(state, setter, e.val)} > - {label === "问题类型" ? issueType[e.val] : maintainability[e.val] || e.val} + + { + label === "缺陷预警提示" ? : + + } + {label === "代码缺陷及风格检查类型" ? issueType[e.val] : maintainability[e.val] || e.val} + {e.count}
    ))} - { showMoreButton && items.length > 10 && { setTagShowMore(!tagShowMore) }}> { tagShowMore ? '收起' : '显示更多'} } + { showMoreButton && items && items.length > 10 && { setTagShowMore(!tagShowMore) }}> { tagShowMore ? '收起' : '显示更多'} }
    ) }; @@ -202,22 +296,32 @@ function IssueList(props) { // 总览项 const renderTotalItem = (label, value, icon) => (
    - {label} - {value} + {label} + {value}
    ); // 总览项(带代码行数) const renderDetailedItem = (label, value, percent, moreValue, icon) => (
    - {label} - + {label} + ({moreValue}{formatNumber(value)}行) + {percent}% - {moreValue}{formatNumber(value)}
    ); + const renderAnalizeItem = (label, type) => { + return
    toIframe(type)} + > + { label } + +
    + } + const toDetail = (e) => { history.push({ pathname: `/${owner}/${projectsId}/service/quality/detail`, @@ -228,6 +332,19 @@ function IssueList(props) { }) } + const toIframe = (type) => { + setCodeSelected(type) + clearAllSelection() + getAnalizeUrl(owner, projectsId).then(res => { + if (res.doxygen_url) { + if (type !== codeAnalyze.func) { + setUrl(`${ res.doxygen_url }/${ type === codeAnalyze.files ? 'files' : 'annotated' }.html`) + } + } + }) + + } + const openAnalysis = () => { setLoading(true) initSonar(true, owner, projectsId).then(res => { @@ -236,6 +353,10 @@ function IssueList(props) { if (res1.status === 0) { message.success('创建分析成功,请等待分析完成') setAnalysisStatus(actionStatus.waiting) + if (timeRef.current) clearTimer(); + timeRef.current = setTimeout(() => { + getAction() + }, 3000); } setLoading(false) }) @@ -252,7 +373,7 @@ function IssueList(props) { return case actionStatus.waiting: case actionStatus.running: - return + return case actionStatus.failure: return

    分析失败,请重试

    @@ -264,95 +385,139 @@ function IssueList(props) { } return ( - -
    - 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, "标签", true)} -
    -
    - {issues && baseInfo.total > 0 && Object.entries(issues).map(([key, item]) => ( -
    -
    - {key.split(':')[1]} -
    - {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 ? ',' : ''} - - ))} - -

    -
    - ))} -
    - ))} - { baseInfo.total > 0 && setPageNum(e)} onShowSizeChange={(current, size) => setPageSize(size)} /> } - {!baseInfo.total &&
    - -

    暂无问题

    -
    } -
    -
    + analysisStatus === actionStatus.success &&
    + 代码质量静态扫描 + 护航研发效能
    } + +
    + 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, "缺陷预警提示")} + {/* 代码执行分析功能固定写死 */} +
    +
    + 代码执行分析功能 +
    + { renderAnalizeItem('classes类溯源', codeAnalyze.classes) } + { renderAnalizeItem('files文件溯源', codeAnalyze.files) } + { renderAnalizeItem('functions溯源', codeAnalyze.func) } + +
    + {renderFilterItems(listFilter.tags, selectedTag, setSelectedTag, "标签", true)} +
    + {/* 右边详情 */} +
    + { + !codeSelected &&
    0 && 'has-issues' }`}> + {issues && baseInfo.total > 0 && Object.entries(issues).map(([key, item]) => ( +
    +
    + + {key.split(':')[1]} +
    + {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 ? ',' : ''} + + ))} + +

    +
    + ))} +
    + ))} + {!baseInfo.total &&
    + +

    暂无问题

    +
    } +
    + } + { + codeSelected && ( + codeSelected !== codeAnalyze.func ? : + ) + } + { !codeSelected && baseInfo.total > 0 && setPageNum(e)} onShowSizeChange={(current, size) => setPageSize(size)} /> } +
    +
    +
    + } +
    ); } -export default IssueList; \ No newline at end of file +export default IssueList; diff --git a/src/forge/Utils/utils.js b/src/forge/Utils/utils.js index 1429ab5e0..085bfd5c6 100644 --- a/src/forge/Utils/utils.js +++ b/src/forge/Utils/utils.js @@ -1,14 +1,14 @@ 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 的数字原样返回 - } + 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 的数字原样返回 + } } export function formatNumberWithCommas(num) { if (num === undefined || num === null) return