From 5927cd62ad1ac20da58a47f331f43c16a57cc93e Mon Sep 17 00:00:00 2001 From: zenggang Date: Thu, 10 Feb 2022 02:20:02 +0000 Subject: [PATCH] * Finish task#48603,48609,48610 --- module/sonarqube/config.php | 2 + module/sonarqube/control.php | 81 ++++++++++++++++++++ module/sonarqube/css/browseissue.css | 5 ++ module/sonarqube/js/browseissue.js | 35 +++++++++ module/sonarqube/lang/en.php | 16 ++++ module/sonarqube/lang/zh-cn.php | 16 +++- module/sonarqube/model.php | 53 ++++++++++++- module/sonarqube/view/browseissue.html.php | 78 +++++++++++++++++++ module/sonarqube/view/browseproject.html.php | 2 +- 9 files changed, 283 insertions(+), 5 deletions(-) create mode 100644 module/sonarqube/css/browseissue.css create mode 100644 module/sonarqube/js/browseissue.js create mode 100644 module/sonarqube/view/browseissue.html.php diff --git a/module/sonarqube/config.php b/module/sonarqube/config.php index a601183ac7..d6b37b8792 100644 --- a/module/sonarqube/config.php +++ b/module/sonarqube/config.php @@ -14,3 +14,5 @@ $config->sonarqube->projectStatusClass['ERROR'] = 'danger'; $config->sonarqube->createproject = new stdclass(); $config->sonarqube->createproject->requiredFields = 'projectName,projectKey'; + +$config->sonarqube->cacheTime = 10; diff --git a/module/sonarqube/control.php b/module/sonarqube/control.php index 43561dc0a7..d8022c3dcb 100644 --- a/module/sonarqube/control.php +++ b/module/sonarqube/control.php @@ -330,4 +330,85 @@ class sonarqube extends control $this->loadModel('action')->create('sonarqubeproject', 0, 'deleted', '', $projectKey); return print(js::reload('parent')); } + + /** + * Browse sonarqube issue. + * + * @param int $sonarqubeID + * @param string $projectKey + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function browseIssue($sonarqubeID, $projectKey = '', $orderBy = 'severity_desc', $recTotal = 0, $recPerPage = 100, $pageID = 1) + { + set_time_limit(600); + + $this->app->loadClass('pager', $static = true); + $keyword = fixer::input('post')->setDefault('keyword', '')->get('keyword'); + + $cacheFile = $this->sonarqube->getCacheFile($sonarqubeID, $projectKey); + if(!$cacheFile or !file_exists($cacheFile) or (time() - filemtime($cacheFile)) / 60 > $this->config->sonarqube->cacheTime) + { + $sonarqubeIssueList = $this->sonarqube->apiGetIssues($sonarqubeID, $projectKey); + + if($cacheFile) + { + if(!file_exists($cacheFile . '.lock')) + { + touch($cacheFile . '.lock'); + file_put_contents($cacheFile, serialize($sonarqubeIssueList)); + unlink($cacheFile . '.lock'); + } + } + } + else + { + $sonarqubeIssueList = unserialize(file_get_contents($cacheFile)); + } + + foreach($sonarqubeIssueList as $key => $sonarqubeIssue) + { + if(!isset($sonarqubeIssue->line)) $sonarqubeIssue->line = ''; + if(!isset($sonarqubeIssue->effort)) $sonarqubeIssue->effort = ''; + $sonarqubeIssue->message = htmlspecialchars($sonarqubeIssue->message); + + list($project, $file) = explode(':', $sonarqubeIssue->component); + $sonarqubeIssue->file = $file; + } + + /* Data search. */ + if($keyword) + { + foreach($sonarqubeIssueList as $key => $sonarqubeIssue) + { + if(strpos($sonarqubeIssue->message, $keyword) === false and strpos($sonarqubeIssue->file, $keyword) === false) unset($sonarqubeIssueList[$key]); + } + $sonarqubeIssueList = array_values($sonarqubeIssueList); + } + + /* Data sort. */ + list($order, $sort) = explode('_', $orderBy); + $orderList = array(); + foreach($sonarqubeIssueList as $sonarqubeIssue) $orderList[] = $sonarqubeIssue->$order; + array_multisort($orderList, $sort == 'desc' ? SORT_DESC : SORT_ASC, $sonarqubeIssueList); + + /* Pager. */ + $this->app->loadClass('pager', $static = true); + $recTotal = count($sonarqubeIssueList); + $pager = new pager($recTotal, $recPerPage, $pageID); + $sonarqubeIssueList = array_chunk($sonarqubeIssueList, $pager->recPerPage); + + $this->view->projectKey = $projectKey; + $this->view->keyword = $keyword; + $this->view->pager = $pager; + $this->view->title = $this->lang->sonarqube->common . $this->lang->colon . $this->lang->sonarqube->browseIssue; + $this->view->sonarqubeID = $sonarqubeID; + $this->view->sonarqubeIssueList = (empty($sonarqubeIssueList) or empty($sonarqubeIssueList[$pageID - 1])) ? array() : $sonarqubeIssueList[$pageID - 1]; + $this->view->orderBy = $orderBy; + $this->display(); + } } diff --git a/module/sonarqube/css/browseissue.css b/module/sonarqube/css/browseissue.css new file mode 100644 index 0000000000..f32e286da6 --- /dev/null +++ b/module/sonarqube/css/browseissue.css @@ -0,0 +1,5 @@ +.text-c-counts > span {margin-left: 5px;} +#mainMenu .pull-left {margin-right: 15px;} +.c-message {width: 350px;} +.c-file {width: 300px;} +#keyword {width: 240px;} diff --git a/module/sonarqube/js/browseissue.js b/module/sonarqube/js/browseissue.js new file mode 100644 index 0000000000..389d567bbd --- /dev/null +++ b/module/sonarqube/js/browseissue.js @@ -0,0 +1,35 @@ +$(document).ready(function() +{ + $('#issueSearch').click(function() + { + triggerSearch(); + }); + + $('#keyword').keypress(function(event) + { + if(event.which == 13) triggerSearch(); + }); + + $('.pager a').each(function() + { + $(this).attr('data-url', $(this).attr('href')); + $(this).attr('href', '###'); + }); + + $('.pager a').click(function() + { + $("#sonarqubeIssueForm").attr('action', $(this).data('url')) + triggerSearch(); + }); +}); + +/** + * Trigger filtering function. + * + * @access public + * @return void + */ +function triggerSearch() +{ + $("#sonarqubeIssueForm").submit(); +} diff --git a/module/sonarqube/lang/en.php b/module/sonarqube/lang/en.php index d6cf51680a..67d0fcaa5b 100644 --- a/module/sonarqube/lang/en.php +++ b/module/sonarqube/lang/en.php @@ -15,6 +15,7 @@ $lang->sonarqube->execJob = "Exec SonarQube Job"; $lang->sonarqube->desc = 'Description'; $lang->sonarqube->reportView = "SonarQube Report"; $lang->sonarqube->browseIssue = "SonarQube Issue List"; +$lang->sonarqube->createBug = "Turn bug"; $lang->sonarqube->id = 'ID'; $lang->sonarqube->name = "Server Name"; @@ -35,6 +36,7 @@ $lang->sonarqube->placeholder->url = "Please fill in the access address $lang->sonarqube->placeholder->account = "Please fill in the SonarQube user information with Administrator privileges"; $lang->sonarqube->placeholder->projectName = 'Up to 255 characters.'; $lang->sonarqube->placeholder->projectKey = "It may contain up to 400 characters. Allowed characters are alphanumeric, '-' (dash), '_' (underscore), '.' (period) and ':' (colon), with at least one non-digit."; +$lang->sonarqube->placeholder->searchIssue = "Please enter the problem name or file"; $lang->sonarqube->nameRepeatError = "Server name already exists!"; $lang->sonarqube->urlRepeatError = 'Server address already exists!'; @@ -62,3 +64,17 @@ $lang->sonarqube->qualitygateList = array(); $lang->sonarqube->qualitygateList['OK'] = 'Passed'; $lang->sonarqube->qualitygateList['WARN'] = 'Warning'; $lang->sonarqube->qualitygateList['ERROR'] = 'Failed'; + +$lang->sonarqube->apiErrorMap[1] = "/Malformed key for Project: '([\s\S]+)'. Allowed characters are alphanumeric, '-', '_', '\.' and ':', with at least one non-digit\./"; + +$lang->sonarqube->errorLang[1] = "Malformed key for Project Key,Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit."; + +$lang->sonarqube->issue = new stdclass(); +$lang->sonarqube->issue->message = 'Issue Name'; +$lang->sonarqube->issue->severity = 'Severity'; +$lang->sonarqube->issue->type = 'Type'; +$lang->sonarqube->issue->status = 'Status'; +$lang->sonarqube->issue->file = 'File'; +$lang->sonarqube->issue->line = 'Line'; +$lang->sonarqube->issue->effort = 'Estimated repair time'; +$lang->sonarqube->issue->creationDate = 'Creation date'; diff --git a/module/sonarqube/lang/zh-cn.php b/module/sonarqube/lang/zh-cn.php index ece4a058e4..0d2be51294 100644 --- a/module/sonarqube/lang/zh-cn.php +++ b/module/sonarqube/lang/zh-cn.php @@ -15,6 +15,7 @@ $lang->sonarqube->execJob = "执行SonarQube任务"; $lang->sonarqube->desc = '描述'; $lang->sonarqube->reportView = "SonarQube报告"; $lang->sonarqube->browseIssue = "SonarQube问题列表"; +$lang->sonarqube->createBug = "转bug"; $lang->sonarqube->id = 'ID'; $lang->sonarqube->name = "服务器名称"; @@ -35,6 +36,7 @@ $lang->sonarqube->placeholder->url = "请填写SonarQube Server首页的 $lang->sonarqube->placeholder->account = "请填写具有Administrator权限的SonarQube用户信息"; $lang->sonarqube->placeholder->projectName = '最多255个字符'; $lang->sonarqube->placeholder->projectKey = "最多400个字符。 允许的字符为字母数字,'-','_','. '和':',至少有一个非数字"; +$lang->sonarqube->placeholder->searchIssue = "请输入问题名称或文件"; $lang->sonarqube->nameRepeatError = "服务器名称已存在!"; $lang->sonarqube->urlRepeatError = "服务器地址已存在!"; @@ -64,5 +66,17 @@ $lang->sonarqube->qualitygateList['WARN'] = 'Warning'; $lang->sonarqube->qualitygateList['ERROR'] = 'Failed'; $lang->sonarqube->apiErrorMap[1] = "/Malformed key for Project: '([\s\S]+)'. Allowed characters are alphanumeric, '-', '_', '\.' and ':', with at least one non-digit\./"; +$lang->sonarqube->apiErrorMap[2] = "/Could not create Project, key already exists: ([\s\S]+)/"; -$lang->sonarqube->errorLang[1] = "项目'%s'的格式不正确。允许的字符为字母数字、'-'、''、'.'和“:”,至少有一个非数字。"; +$lang->sonarqube->errorLang[1] = "项目标识的格式不正确。允许的字符为字母数字、'-'、''、'.'和“:”,至少有一个非数字。"; +$lang->sonarqube->errorLang[2] = "无法创建项目,项目标识已存在:%s"; + +$lang->sonarqube->issue = new stdclass(); +$lang->sonarqube->issue->message = '问题名称'; +$lang->sonarqube->issue->severity = '严重程度'; +$lang->sonarqube->issue->type = '类型'; +$lang->sonarqube->issue->status = '状态'; +$lang->sonarqube->issue->file = '所属文件'; +$lang->sonarqube->issue->line = '行数'; +$lang->sonarqube->issue->effort = '预计修复时长'; +$lang->sonarqube->issue->creationDate = '创建日期'; diff --git a/module/sonarqube/model.php b/module/sonarqube/model.php index 5ed62474a2..6dad63f4da 100644 --- a/module/sonarqube/model.php +++ b/module/sonarqube/model.php @@ -89,6 +89,35 @@ class sonarqubeModel extends model return json_decode(commonModel::http($url, null, array(), $header)); } + /** + * Get issues of one sonarqube. + * + * @param int $sonarqubeID + * @param string $projectKey + * @access public + * @return array + */ + public function apiGetIssues($sonarqubeID, $projectKey = '') + { + list($apiRoot, $header) = $this->getApiBase($sonarqubeID); + if(!$apiRoot) return array(); + + $url = sprintf($apiRoot, "issues/search"); + $url .= "?ps=500"; + if($projectKey) $url .= "&componentKeys={$projectKey}"; + + $allResults = array(); + for($page = 1; true; $page++) + { + $result = json_decode(commonModel::http($url . "&p={$page}", null, array(), $header)); + if(!isset($result->issues)) break; + if(!empty($result->issues)) $allResults = array_merge($allResults, $result->issues); + if(count($result->issues) < 500) break; + } + + return $allResults; + } + /** * Get projects of one sonarqube. * @@ -110,8 +139,7 @@ class sonarqubeModel extends model $allResults = array(); for($page = 1; true; $page++) { - $url .= "&p={$page}"; - $result = json_decode(commonModel::http($url, null, array(), $header)); + $result = json_decode(commonModel::http($url . "&p={$page}", null, array(), $header)); if(!isset($result->components)) break; if(!empty($result->components)) $allResults = array_merge($allResults, $result->components); if(count($result->components) < 500) break; @@ -168,7 +196,10 @@ class sonarqubeModel extends model { $project = fixer::input('post')->get(); - $this->dao->insert('sonarqube')->data($project)->batchCheck($this->config->sonarqube->createproject->requiredFields, 'notempty'); + $this->dao->insert('sonarqube')->data($project) + ->batchCheck($this->config->sonarqube->createproject->requiredFields, 'notempty') + ->check('projectName', 'length', 255, 1) + ->check('projectKey', 'length', 400, 1); if(dao::isError()) return false; $response = $this->apiCreateProject($sonarqubeID, $project); @@ -235,4 +266,20 @@ class sonarqubeModel extends model } return isset($errorMessage) ? $errorMessage : $message; } + + /** + * Get cache file. + * + * @param int $sonarqubeID + * @param string $projectKey + * @access public + * @return string + */ + public function getCacheFile($sonarqubeID, $projectKey) + { + $cachePath = $this->app->getCacheRoot() . '/' . 'sonarqube'; + if(!is_dir($cachePath)) mkdir($cachePath, 0777, true); + if(!is_writable($cachePath)) return false; + return $cachePath . '/' . $sonarqubeID . '-' . md5($projectKey); + } } diff --git a/module/sonarqube/view/browseissue.html.php b/module/sonarqube/view/browseissue.html.php new file mode 100644 index 0000000000..fa5b14651b --- /dev/null +++ b/module/sonarqube/view/browseissue.html.php @@ -0,0 +1,78 @@ + + * @package sonarqube + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
+

+ noData;?> +

+
+ +
+
+ + recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}";?> + + + + + + + + + + + + + + $sonarqubeIssue): ?> + + + + + + + + + + + + +
sonarqube->issue->message);?>sonarqube->issue->severity);?>sonarqube->issue->type);?>sonarqube->issue->status);?>sonarqube->issue->file);?>sonarqube->issue->line);?>sonarqube->issue->effort);?>actions;?>
message;?>severity;?>type;?>status;?>file;?>line;?>effort;?> + ", '', "title='{$lang->sonarqube->createBug}' class='btn btn-primary'"); + ?> +
+ + + +
+
+ + diff --git a/module/sonarqube/view/browseproject.html.php b/module/sonarqube/view/browseproject.html.php index 7dd3cbedad..5d61030b09 100644 --- a/module/sonarqube/view/browseproject.html.php +++ b/module/sonarqube/view/browseproject.html.php @@ -27,7 +27,7 @@
- " . $lang->sonarqube->createProject, '', "class='btn btn-primary'");?> + " . $lang->sonarqube->createProject, '', "class='btn btn-primary'");?>