* Finish task#48603,48609,48610
This commit is contained in:
@@ -14,3 +14,5 @@ $config->sonarqube->projectStatusClass['ERROR'] = 'danger';
|
||||
|
||||
$config->sonarqube->createproject = new stdclass();
|
||||
$config->sonarqube->createproject->requiredFields = 'projectName,projectKey';
|
||||
|
||||
$config->sonarqube->cacheTime = 10;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = '创建日期';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* The browse view file of sonarqube module of ZenTaoPMS.
|
||||
*
|
||||
* @copyright Copyright 2009-2022 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
|
||||
* @license ZPL (http://zpl.pub/page/zplv12.html)
|
||||
* @author Gang Zeng <zenggang@cnezsoft.com>
|
||||
* @package sonarqube
|
||||
* @version $Id$
|
||||
* @link http://www.zentao.net
|
||||
*/
|
||||
?>
|
||||
<?php include '../../common/view/header.html.php';?>
|
||||
<div id="mainMenu" class="clearfix">
|
||||
<div class='pull-left'>
|
||||
<?php echo html::a($this->createLink('sonarqube', 'browseProject', "sonarqubeID={$sonarqubeID}"), "<i class='icon icon-back icon-sm'></i> " . $lang->goback, '', "class='btn btn-secondary'");?>
|
||||
</div>
|
||||
<div id="sidebarHeader">
|
||||
<div class="title"><?php echo $projectKey; ?></div>
|
||||
</div>
|
||||
<div class="btn-toolbar pull-left">
|
||||
<div>
|
||||
<form id='sonarqubeIssueForm' method='post'>
|
||||
<?php echo html::input('keyword', $keyword, "class='form-control' placeholder='{$lang->sonarqube->placeholder->searchIssue}' style='display: inline-block;margin:0 10px'");?>
|
||||
<a id="issueSearch" class="btn btn-primary"><?php echo $lang->sonarqube->search?></a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php if(empty($sonarqubeIssueList)):?>
|
||||
<div class="table-empty-tip">
|
||||
<p>
|
||||
<span class="text-muted"><?php echo $lang->noData;?></span>
|
||||
</p>
|
||||
</div>
|
||||
<?php else:?>
|
||||
<div id='mainContent' class='main-row'>
|
||||
<form class='main-table' id='ajaxForm' method='post'>
|
||||
<table id='sonarqubeIssueList' class='table has-sort-head table-fixed'>
|
||||
<?php $vars = "sonarqubeID={$sonarqubeID}&projectKey={$projectKey}&orderBy=%s&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}";?>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class='c-message text-left'><?php common::printOrderLink('message', $orderBy, $vars, $lang->sonarqube->issue->message);?></th>
|
||||
<th><?php common::printOrderLink('severity', $orderBy, $vars, $lang->sonarqube->issue->severity);?></th>
|
||||
<th><?php common::printOrderLink('type', $orderBy, $vars, $lang->sonarqube->issue->type);?></th>
|
||||
<th><?php common::printOrderLink('status', $orderBy, $vars, $lang->sonarqube->issue->status);?></th>
|
||||
<th class='c-file text-left'><?php common::printOrderLink('file', $orderBy, $vars, $lang->sonarqube->issue->file);?></th>
|
||||
<th><?php common::printOrderLink('line', $orderBy, $vars, $lang->sonarqube->issue->line);?></th>
|
||||
<th><?php common::printOrderLink('effort', $orderBy, $vars, $lang->sonarqube->issue->effort);?></th>
|
||||
<th class='c-actions-2'><?php echo $lang->actions;?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($sonarqubeIssueList as $id => $sonarqubeIssue): ?>
|
||||
<tr class='text'>
|
||||
<td class='text' title="<?php echo $sonarqubeIssue->message;?>"><?php echo $sonarqubeIssue->message;?></td>
|
||||
<td class='text-c-name' title='<?php echo $sonarqubeIssue->severity;?>'><?php echo $sonarqubeIssue->severity;?></td>
|
||||
<td class='text-c-name' title='<?php echo $sonarqubeIssue->type;?>'><?php echo $sonarqubeIssue->type;?></td>
|
||||
<td class='text-c-name' title='<?php echo $sonarqubeIssue->status;?>'><?php echo $sonarqubeIssue->status;?></td>
|
||||
<td class='text' title='<?php echo $sonarqubeIssue->file;?>'><?php echo $sonarqubeIssue->file;?></td>
|
||||
<td class='text-c-name' title='<?php echo $sonarqubeIssue->line;?>'><?php echo $sonarqubeIssue->line;?></td>
|
||||
<td class='text-c-name' title='<?php echo $sonarqubeIssue->effort;?>'><?php echo $sonarqubeIssue->effort;?></td>
|
||||
<td class='c-actions text-left'>
|
||||
<?php
|
||||
common::printLink('bug', 'create', '', "<i class='icon-testcase-createBug icon-bug'></i> ", '', "title='{$lang->sonarqube->createBug}' class='btn btn-primary'");
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach;?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php if($sonarqubeIssueList):?>
|
||||
<div class='table-footer'><?php $pager->show('right', 'pagerjs');?></div>
|
||||
<?php endif;?>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif;?>
|
||||
<?php include '../../common/view/footer.html.php';?>
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-toolbar pull-right">
|
||||
<?php if(common::hasPriv('sonarqube', 'createProject')) common::printLink('sonarqube', 'createProject', "sonarqubeID=$sonarqubeID", "<i class='icon icon-plus'></i> " . $lang->sonarqube->createProject, '', "class='btn btn-primary'");?>
|
||||
<?php common::printLink('sonarqube', 'createProject', "sonarqubeID=$sonarqubeID", "<i class='icon icon-plus'></i> " . $lang->sonarqube->createProject, '', "class='btn btn-primary'");?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if(empty($sonarqubeProjectList)):?>
|
||||
|
||||
Reference in New Issue
Block a user