diff --git a/config/zentaopms.php b/config/zentaopms.php index b7b187ba3a..50a3eaf890 100644 --- a/config/zentaopms.php +++ b/config/zentaopms.php @@ -292,6 +292,9 @@ $config->objectTables['kanbanlane'] = TABLE_KANBANLANE; $config->objectTables['kanbanorder'] = TABLE_KANBANORDER; $config->objectTables['kanbangroup'] = TABLE_KANBANGROUP; $config->objectTables['kanbancard'] = TABLE_KANBANCARD; +$config->objectTables['sonarqube'] = TABLE_PIPELINE; +$config->objectTables['gitlab'] = TABLE_PIPELINE; +$config->objectTables['jebkins'] = TABLE_PIPELINE; $config->newFeatures = array('introduction', 'tutorial', 'youngBlueTheme'); diff --git a/db/update16.2.sql b/db/update16.2.sql index 120a2d3779..75c4536ef7 100644 --- a/db/update16.2.sql +++ b/db/update16.2.sql @@ -1,3 +1,6 @@ ALTER TABLE `zt_kanbanspace` ADD `type` varchar(50) NOT NULL AFTER `name`; UPDATE `zt_kanbanspace` SET `type` = 'cooperation'; ALTER TABLE `zt_kanban` ADD `performable` enum ('0', '1') NOT NULL DEFAULT '0' AFTER `archived`; + +ALTER TABLE `zt_job` ADD `sonarqubeServer` mediumint(8) unsigned NOT NULL AFTER `triggerType`; +ALTER TABLE `zt_job` ADD `projectKey` varchar(255) NOT NULL AFTER `sonarqubeServer`; \ No newline at end of file diff --git a/module/action/config.php b/module/action/config.php index c40a551519..940dbfaf97 100755 --- a/module/action/config.php +++ b/module/action/config.php @@ -40,6 +40,7 @@ $config->action->objectNameFields['kanbanspace'] = 'name'; $config->action->objectNameFields['kanbanregion'] = 'name'; $config->action->objectNameFields['kanban'] = 'name'; $config->action->objectNameFields['kanbancard'] = 'name'; +$config->action->objectNameFields['sonarqube'] = 'name'; $config->action->commonImgSize = 870; @@ -55,4 +56,4 @@ $config->action->majorList['execution'] = array('opened', 'edited'); $config->action->needGetProjectType = 'build,task,bug,case,testcase,caselib,testtask,testsuite,testreport,doc,issue,release,risk,design,opportunity,trainplan,gapanalysis,researchplan,researchreport,'; $config->action->needGetRelateField = ',story,productplan,release,task,build,bug,case,testtask,testreport,doc,doclib,issue,risk,opportunity,trainplan,gapanalysis,team,whitelist,researchplan,researchreport,meeting,kanbanlane,kanbancolumn,'; -$config->action->noLinkModules = ',doclib,module,webhook,gitlab,pipeline,jenkins,kanban,kanbanspace,kanbancolumn,kanbanlane,kanbanregion,kanbancard,execution,project,'; +$config->action->noLinkModules = ',doclib,module,webhook,gitlab,sonarqube,pipeline,jenkins,kanban,kanbanspace,kanbancolumn,kanbanlane,kanbanregion,kanbancard,execution,project,'; diff --git a/module/action/lang/en.php b/module/action/lang/en.php index 93f9ddca4d..bde1c19b3d 100755 --- a/module/action/lang/en.php +++ b/module/action/lang/en.php @@ -134,6 +134,8 @@ $lang->action->objectTypes['kanbanregion'] = 'Kanban Region'; $lang->action->objectTypes['kanbanlane'] = 'Kanban Lane'; $lang->action->objectTypes['kanbancolumn'] = 'Kanban Column'; $lang->action->objectTypes['kanbancard'] = 'Kanban Card'; +$lang->action->objectTypes['sonarqube'] = 'SonarQube Server'; +$lang->action->objectTypes['sonarqubeproject'] = 'SonarQube Project'; /* Used to describe operation history. */ $lang->action->desc = new stdclass(); @@ -329,6 +331,7 @@ $lang->action->label->finishedbychild = 'finished'; $lang->action->label->closedbychild = 'closed'; $lang->action->label->activatedbychild = 'activated'; $lang->action->label->createchild = 'activated'; +$lang->action->label->executed = 'executed'; /* Dynamic information is grouped by object. */ $lang->action->dynamicAction = new stdclass; @@ -568,6 +571,22 @@ $lang->action->dynamicAction->user['loginxuanxuan'] = 'Login Desktop'; $lang->action->dynamicAction->entry['created'] = 'Add Application'; $lang->action->dynamicAction->entry['edited'] = 'Edit Application'; +$lang->action->dynamicAction->job['created'] = 'Create Job'; +$lang->action->dynamicAction->job['edited'] = 'Edit Job'; +$lang->action->dynamicAction->job['executed'] = 'Execute Job'; +$lang->action->dynamicAction->job['deleted'] = 'Delete Job'; +$lang->action->dynamicAction->job['undeleted'] = 'Undelete Job'; + +$lang->action->dynamicAction->sonarqube['created'] = 'Create SonarQube Server'; +$lang->action->dynamicAction->sonarqube['edited'] = 'Edit SonarQube Server'; +$lang->action->dynamicAction->sonarqube['deleted'] = 'Delete SonarQube Server'; + +$lang->action->dynamicAction->sonarqubeproject['deleted'] = 'Delete SonarQube Project'; + +$lang->action->dynamicAction->gitlab['created'] = 'Create GitLab Server'; +$lang->action->dynamicAction->gitlab['edited'] = 'Edit GitLab Server'; +$lang->action->dynamicAction->gitlab['deleted'] = 'Delete GitLab Server'; + /* Generate the corresponding object link. */ $lang->action->label->product = $lang->productCommon . '|product|view|productID=%s'; $lang->action->label->productplan = 'Plan|productplan|view|productID=%s'; diff --git a/module/action/lang/zh-cn.php b/module/action/lang/zh-cn.php index 13e034b34c..98679d3045 100755 --- a/module/action/lang/zh-cn.php +++ b/module/action/lang/zh-cn.php @@ -134,6 +134,8 @@ $lang->action->objectTypes['kanbanregion'] = '看板区域'; $lang->action->objectTypes['kanbanlane'] = '看板泳道'; $lang->action->objectTypes['kanbancolumn'] = '看板列'; $lang->action->objectTypes['kanbancard'] = '看板卡片'; +$lang->action->objectTypes['sonarqube'] = 'SonarQube服务器'; +$lang->action->objectTypes['sonarqubeproject'] = 'SonarQube项目'; /* 用来描述操作历史记录。*/ $lang->action->desc = new stdclass(); @@ -329,6 +331,7 @@ $lang->action->label->finishedbychild = '完成了'; $lang->action->label->closedbychild = '关闭了'; $lang->action->label->activatedbychild = '激活了'; $lang->action->label->createchild = '激活了'; +$lang->action->label->executed = '执行了'; /* 动态信息按照对象分组 */ $lang->action->dynamicAction = new stdclass(); @@ -568,6 +571,22 @@ $lang->action->dynamicAction->user['loginxuanxuan'] = '登录客户端'; $lang->action->dynamicAction->entry['created'] = '添加应用'; $lang->action->dynamicAction->entry['edited'] = '编辑应用'; +$lang->action->dynamicAction->job['created'] = '创建构建任务'; +$lang->action->dynamicAction->job['edited'] = '编辑构建任务'; +$lang->action->dynamicAction->job['executed'] = '执行构建任务'; +$lang->action->dynamicAction->job['deleted'] = '删除构建任务'; +$lang->action->dynamicAction->job['undeleted'] = '还原构建任务'; + +$lang->action->dynamicAction->sonarqube['created'] = '创建SonarQube服务器'; +$lang->action->dynamicAction->sonarqube['edited'] = '设置SonarQube服务器'; +$lang->action->dynamicAction->sonarqube['deleted'] = '删除SonarQube服务器'; + +$lang->action->dynamicAction->sonarqubeproject['deleted'] = '删除SonarQube项目'; + +$lang->action->dynamicAction->gitlab['created'] = '创建GitLab服务器'; +$lang->action->dynamicAction->gitlab['edited'] = '设置GitLab服务器'; +$lang->action->dynamicAction->gitlab['deleted'] = '删除GitLab服务器'; + /* 用来生成相应对象的链接。*/ $lang->action->label->product = $lang->productCommon . '|product|view|productID=%s'; $lang->action->label->productplan = "计划|productplan|view|productID=%s"; diff --git a/module/block/view/dynamic.html.php b/module/block/view/dynamic.html.php index 215bb804d3..51f529e8fe 100644 --- a/module/block/view/dynamic.html.php +++ b/module/block/view/dynamic.html.php @@ -13,6 +13,7 @@ { $user = zget($users, $action->actor); if($action->action == 'login' or $action->action == 'logout') $action->objectName = $action->objectLabel = ''; + if($action->objectType == 'sonarqubeproject') $action->objectName = $action->extra; $class = $action->major ? "class='active'" : ''; echo "
  • "; if($action->objectLink) printf($lang->block->dynamicInfo, $action->date, $user, $action->actionLabel, $action->objectLabel, $action->objectLink, $action->objectName, $action->objectName); diff --git a/module/common/lang/menu.php b/module/common/lang/menu.php index 547d96b935..fedc8779b1 100644 --- a/module/common/lang/menu.php +++ b/module/common/lang/menu.php @@ -386,22 +386,24 @@ $lang->qa->menuOrder[45] = 'automation'; $lang->qa->dividerMenu = ',bug,testtask,caselib,'; /* DevOps menu. */ -$lang->devops->menu = new stdclass(); -$lang->devops->menu->code = array('link' => "{$lang->repo->common}|repo|browse|repoID=%s", 'alias' => 'diff,view,revision,log,blame,showsynccommit'); -$lang->devops->menu->compile = array('link' => "{$lang->devops->compile}|job|browse", 'subModule' => 'compile,job'); -$lang->devops->menu->mr = array('link' => "{$lang->devops->mr}|mr|browse"); -$lang->devops->menu->gitlab = array('link' => "GitLab|gitlab|browse", 'alias' => 'create,edit'); -$lang->devops->menu->jenkins = array('link' => "Jenkins|jenkins|browse", 'alias' => 'create,edit'); -$lang->devops->menu->maintain = array('link' => "{$lang->devops->repo}|repo|maintain", 'alias' => 'create,edit'); -$lang->devops->menu->rules = array('link' => "{$lang->devops->rules}|repo|setrules"); +$lang->devops->menu = new stdclass(); +$lang->devops->menu->code = array('link' => "{$lang->repo->common}|repo|browse|repoID=%s", 'alias' => 'diff,view,revision,log,blame,showsynccommit'); +$lang->devops->menu->compile = array('link' => "{$lang->devops->compile}|job|browse", 'subModule' => 'compile,job'); +$lang->devops->menu->mr = array('link' => "{$lang->devops->mr}|mr|browse"); +$lang->devops->menu->gitlab = array('link' => "GitLab|gitlab|browse", 'alias' => 'create,edit'); +$lang->devops->menu->sonarqube = array('link' => "SonarQube|sonarqube|browse"); +$lang->devops->menu->jenkins = array('link' => "Jenkins|jenkins|browse", 'alias' => 'create,edit'); +$lang->devops->menu->maintain = array('link' => "{$lang->devops->repo}|repo|maintain", 'alias' => 'create,edit'); +$lang->devops->menu->rules = array('link' => "{$lang->devops->rules}|repo|setrules"); $lang->devops->menuOrder[5] = 'code'; $lang->devops->menuOrder[10] = 'compile'; $lang->devops->menuOrder[15] = 'mr'; $lang->devops->menuOrder[20] = 'gitlab'; -$lang->devops->menuOrder[25] = 'jenkins'; -$lang->devops->menuOrder[30] = 'maintain'; -$lang->devops->menuOrder[35] = 'rules'; +$lang->devops->menuOrder[25] = 'sonarqube'; +$lang->devops->menuOrder[30] = 'jenkins'; +$lang->devops->menuOrder[35] = 'maintain'; +$lang->devops->menuOrder[40] = 'rules'; /* Kanban menu. */ $lang->kanban->menu = new stdclass(); @@ -618,16 +620,18 @@ $lang->navGroup->testtask = 'qa'; $lang->navGroup->testsuite = 'qa'; $lang->navGroup->caselib = 'qa'; -$lang->navGroup->devops = 'devops'; -$lang->navGroup->repo = 'devops'; -$lang->navGroup->job = 'devops'; -$lang->navGroup->jenkins = 'devops'; -$lang->navGroup->mr = 'devops'; -$lang->navGroup->gitlab = 'devops'; -$lang->navGroup->compile = 'devops'; -$lang->navGroup->ci = 'devops'; -$lang->navGroup->svn = 'devops'; -$lang->navGroup->git = 'devops'; +$lang->navGroup->devops = 'devops'; +$lang->navGroup->repo = 'devops'; +$lang->navGroup->job = 'devops'; +$lang->navGroup->jenkins = 'devops'; +$lang->navGroup->mr = 'devops'; +$lang->navGroup->gitlab = 'devops'; +$lang->navGroup->sonarqube = 'devops'; +$lang->navGroup->sonarqubeproject = 'devops'; +$lang->navGroup->compile = 'devops'; +$lang->navGroup->ci = 'devops'; +$lang->navGroup->svn = 'devops'; +$lang->navGroup->git = 'devops'; $lang->navGroup->company = 'system'; diff --git a/module/company/view/dynamic.html.php b/module/company/view/dynamic.html.php index ab820eed0b..d07b63407d 100644 --- a/module/company/view/dynamic.html.php +++ b/module/company/view/dynamic.html.php @@ -66,10 +66,14 @@ objectType == 'meeting') $tab = $action->project ? "data-app='project'" : "data-app='my'";?> objectName)) + if(empty($action->objectName) and $action->objectID) { echo '#' . $action->objectID; } + elseif(empty($action->objectID) and $action->extra) + { + echo $action->extra; + } elseif(empty($action->objectLink)) { echo $action->objectName; @@ -79,8 +83,10 @@ echo html::a($action->objectLink, $action->objectName, '', $tab); } ?> + objectID):?> objectID;?> +
  • diff --git a/module/gitlab/view/browse.html.php b/module/gitlab/view/browse.html.php index b682227146..137e6d80b0 100644 --- a/module/gitlab/view/browse.html.php +++ b/module/gitlab/view/browse.html.php @@ -32,7 +32,7 @@ recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}";?> - gitlab->id);?> + gitlab->id);?> gitlab->name);?> gitlab->url);?> actions;?> diff --git a/module/group/lang/resource.php b/module/group/lang/resource.php index c28fbfb220..0a0706d26a 100644 --- a/module/group/lang/resource.php +++ b/module/group/lang/resource.php @@ -1384,6 +1384,24 @@ $lang->gitlab->methodOrder[135] = 'browseTag'; $lang->gitlab->methodOrder[140] = 'browseTagPriv'; $lang->gitlab->methodOrder[145] = 'deleteTagPriv'; +/* SonarQube. */ +$lang->resource->sonarqube = new stdclass(); +$lang->resource->sonarqube->browse = 'browse'; +$lang->resource->sonarqube->create = 'create'; +$lang->resource->sonarqube->edit = 'edit'; +$lang->resource->sonarqube->delete = 'delete'; +$lang->resource->sonarqube->browseProject = 'browseProject'; +$lang->resource->sonarqube->deleteProject = 'deleteProject'; +$lang->resource->sonarqube->execJob = 'execJob'; + +$lang->sonarqube->methodOrder[5] = 'browse'; +$lang->sonarqube->methodOrder[10] = 'create'; +$lang->sonarqube->methodOrder[15] = 'edit'; +$lang->sonarqube->methodOrder[20] = 'delete'; +$lang->sonarqube->methodOrder[25] = 'browseProject'; +$lang->sonarqube->methodOrder[35] = 'deleteProject'; +$lang->sonarqube->methodOrder[40] = 'execJob'; + /* merge request. */ $lang->resource->mr = new stdclass(); $lang->resource->mr->create = 'create'; @@ -1728,12 +1746,12 @@ $lang->backup->methodOrder[25] = 'setting'; $lang->backup->methodOrder[30] = 'rmPHPHeader'; $lang->resource->cron = new stdclass(); -$lang->resource->cron->index = 'index'; -$lang->resource->cron->turnon = 'turnon'; -$lang->resource->cron->create = 'createAction'; -$lang->resource->cron->edit = 'edit'; -$lang->resource->cron->toggle = 'toggle'; -$lang->resource->cron->delete = 'delete'; +$lang->resource->cron->index = 'index'; +$lang->resource->cron->turnon = 'turnon'; +$lang->resource->cron->create = 'createAction'; +$lang->resource->cron->edit = 'edit'; +$lang->resource->cron->toggle = 'toggle'; +$lang->resource->cron->delete = 'delete'; $lang->resource->cron->openProcess = 'restart'; $lang->cron->methodOrder[5] = 'index'; diff --git a/module/job/control.php b/module/job/control.php index 961f71c525..332b0a695a 100644 --- a/module/job/control.php +++ b/module/job/control.php @@ -88,6 +88,8 @@ class job extends control } return $this->send(array('result' => 'fail', 'message' => $errors)); } + + $this->loadModel('action')->create('job', $jobID, 'created'); if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $jobID)); return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse'))); } @@ -120,7 +122,8 @@ class job extends control $this->view->repoTypes = $repoTypes; $this->view->products = array(0 => '') + $this->loadModel('product')->getProductPairsByProject($this->projectID); - $this->view->jenkinsServerList = array('' => '') + $this->loadModel('jenkins')->getPairs(); + $this->view->jenkinsServerList = array('' => '') + $this->loadModel('jenkins')->getPairs(); + $this->view->sonarqubeServerList = array('') + $this->loadModel('pipeline')->getPairs('sonarqube'); $this->display(); } @@ -162,6 +165,8 @@ class job extends control } return $this->send(array('result' => 'fail', 'message' => $errors)); } + + $this->loadModel('action')->create('job', $id, 'edited'); return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse'))); } @@ -189,17 +194,18 @@ class job extends control if($jobProduct and $jobProduct->deleted == 0) $products += array($job->product => $jobProduct->name); } - $this->view->title = $this->lang->ci->job . $this->lang->colon . $this->lang->job->edit; - $this->view->position[] = html::a(inlink('browse'), $this->lang->ci->job); - $this->view->position[] = $this->lang->job->edit; - $this->view->repoPairs = $repoPairs; - $this->view->gitlabRepos = $gitlabRepos; - $this->view->repoTypes = $repoTypes; - $this->view->repoType = zget($repoTypes, $job->repo, 'Git'); - $this->view->job = $job; - $this->view->products = array(0 => '') + $products; - $this->view->jenkinsServerList = $this->loadModel('jenkins')->getPairs(); - $this->view->pipelines = $this->jenkins->getTasks($job->server); + $this->view->title = $this->lang->ci->job . $this->lang->colon . $this->lang->job->edit; + $this->view->position[] = html::a(inlink('browse'), $this->lang->ci->job); + $this->view->position[] = $this->lang->job->edit; + $this->view->repoPairs = $repoPairs; + $this->view->gitlabRepos = $gitlabRepos; + $this->view->repoTypes = $repoTypes; + $this->view->repoType = zget($repoTypes, $job->repo, 'Git'); + $this->view->job = $job; + $this->view->products = array('') + $products; + $this->view->jenkinsServerList = $this->loadModel('jenkins')->getPairs(); + $this->view->sonarqubeServerList = array('') + $this->loadModel('pipeline')->getPairs('sonarqube'); + $this->view->pipelines = $this->jenkins->getTasks($job->server); $this->display(); } @@ -323,6 +329,7 @@ class job extends control if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); $this->app->loadLang('compile'); + $this->loadModel('action')->create('job', $id, 'executed'); return $this->send(array('result' => 'success', 'message' => sprintf($this->lang->job->sendExec, zget($this->lang->compile->statusList, $compile->status)))); } @@ -414,4 +421,21 @@ class job extends control $this->send(array('result' => 'success', 'type' => strtolower($repo->SCM))); } + /** + * Ajax check SonarQube linked by repoID. + * + * @param int $repoID + * @access public + * @return void + */ + public function ajaxCheckSonarqubeLink($repoID, $jobID = 0) + { + $repo = $this->loadModel('job')->getSonarqubeByRepo(array($repoID), $jobID, true); + if(!empty($repo)) + { + $message = $repo[$repoID]->deleted ? $this->lang->job->jobIsDeleted : sprintf($this->lang->job->repoExists, $repo[$repoID]->id . '-' . $repo[$repoID]->name); + $this->send(array('result' => 'fail', 'message' => $message)); + } + $this->send(array('result' => 'success', 'message' => '')); + } } diff --git a/module/job/js/create.js b/module/job/js/create.js index 635ce8abc2..cd188907e9 100644 --- a/module/job/js/create.js +++ b/module/job/js/create.js @@ -1,5 +1,51 @@ $(document).ready(function() { + /* + * Get frame select. + * param string engine eg. jenkins|gitlab + */ + function getFrameSelect(engine) + { + $('#frameBox .input-group').empty(); + $('#frameBox .input-group').append("
    "); + var html = "'; + + $('#frameBox .loading').remove(); + $('#frameBox .input-group').append(html); + $('#frameBox #frame').chosen(); + + $('#frame').change(); + } + getFrameSelect(''); + + /* + * Check sonarqube linked. + */ + function checkSonarquebLink() + { + var repoID = $('#repo').val(); + var frame = $('#frame').val(); + + if(frame != 'sonarqube' || repoID == 0) return false; + + $.getJSON(createLink('job', 'ajaxCheckSonarqubeLink', 'repoID=' + repoID), function(result) + { + if(result.result != 'success') + { + alert(result.message); + $('#repo').val(0).trigger('chosen:updated'); + $('#reference').val('').trigger('chosen:updated'); + $('.reference').hide(); + return false; + } + }) + } + $(document).on('change', '#repo', function() { var repoID = $(this).val(); @@ -63,6 +109,9 @@ $(document).ready(function() $('#triggerggerType option[value=tag]').html(data.type == 'gitlab' ? buildTag : dirChange).trigger('chosen:updated'); } }); + + /* Check exists sonarqube data. */ + checkSonarquebLink(); }); $(document).on('change', '[name^=svnDir]', function() @@ -139,6 +188,42 @@ $(document).ready(function() $('#jenkinsServerTR #jkTask').chosen({drop_direction: 'auto'}); }) + + /* There has been a problem with handling the prompt label. */ + $('#jkTaskLabel').remove(); + }) + + $(document).on('change', '#frame', function() + { + var frame = $(this).val(); + if(frame == 'sonarqube') + { + $('tr.sonarqube').removeClass('hide'); + + /* Check exists sonarqube data. */ + checkSonarquebLink(); + } + else + { + $('tr.sonarqube').addClass('hide'); + } + }) + + $(document).on('change', '#sonarqubeServer', function() + { + var sonarqubeID = $(this).val(); + $('#sonarProject #projectKey').remove(); + $('#sonarProject #projectKey_chosen').remove(); + $('#sonarProject .input-group').append("
    "); + $.get(createLink('sonarqube', 'ajaxGetProjectList', 'sonarqubeID=' + sonarqubeID), function(html) + { + $('#sonarProject .loading').remove(); + $('#sonarProject .input-group').append(html); + $('#sonarProject #projectKey').chosen({drop_direction: 'auto'}); + }) + + /* There has been a problem with handling the prompt label. */ + $('#projectKeyLabel').remove(); }) var scheduleOption = ""; @@ -161,9 +246,10 @@ $(document).ready(function() $('tr.gitlabRepo').hide(); $('tr.commonRepo').show(); } + + getFrameSelect(engine); }); $('#engine').change(); - $('#triggerType').change(); }); diff --git a/module/job/js/edit.js b/module/job/js/edit.js index f91c2aef1b..66066ae271 100644 --- a/module/job/js/edit.js +++ b/module/job/js/edit.js @@ -1,5 +1,52 @@ $(document).ready(function() { + /* + * Get frame select. + * param string engine eg. jenkins|gitlab + */ + function getFrameSelect() + { + $('#frameBox .input-group').empty(); + $('#frameBox .input-group').append("
    "); + var html = "'; + + $('#frameBox .loading').remove(); + $('#frameBox .input-group').append(html); + $('#frameBox #frame').chosen(); + } + getFrameSelect(); + + /* Check sonarqube linked. */ + function checkSonarquebLink() + { + var repoID = $('#repo').val(); + var frame = $('#frame').val(); + + if(frame != 'sonarqube' || repoID == 0) return false; + + $.getJSON(createLink('job', 'ajaxCheckSonarqubeLink', 'repoID=' + repoID + '&jobID=' + job.id), function(result) + { + if(result.result != 'success') + { + alert(result.message); + $('#repo').val(0).trigger('chosen:updated'); + $('#reference').val('').trigger('chosen:updated'); + $('.reference').hide(); + return false; + } + }) + } + $('#gitlabRepo').change(function() { $('#repo').val($(this).val()).change(); @@ -45,6 +92,9 @@ $(document).ready(function() $('#svnDirBox #svnDir').chosen(); }) } + + /* Check exists sonarqube data. */ + checkSonarquebLink(); }) $(document).on('change', '[name^=svnDir]', function() @@ -124,6 +174,43 @@ $(document).ready(function() $('#jenkinsServerTR #jkTask').val(jkTask).chosen(); }) + + /* There has been a problem with handling the prompt label. */ + $('#jkTaskLabel').remove(); + }) + + $(document).on('change', '#frame', function() + { + var frame = $(this).val(); + if(frame == 'sonarqube') + { + $('tr.sonarqube').removeClass('hide'); + + /* Check exists sonarqube data. */ + checkSonarquebLink(); + } + else + { + $('tr.sonarqube').addClass('hide'); + } + }) + + $(document).on('change', '#sonarqubeServer', function() + { + var sonarqubeID = $(this).val(); + var projectKey = sonarqubeID == job.sonarqubeServer ? job.projectKey.replace(/-/g, '*') : ''; + $('#sonarProject #projectKey').remove(); + $('#sonarProject #projectKey_chosen').remove(); + $('#sonarProject .input-group').append("
    "); + $.get(createLink('sonarqube', 'ajaxGetProjectList', 'sonarqubeID=' + sonarqubeID + '&projectKey=' + projectKey), function(html) + { + $('#sonarProject .loading').remove(); + $('#sonarProject .input-group').append(html); + $('#sonarProject #projectKey').chosen({drop_direction: 'auto'}); + }) + + /* There has been a problem with handling the prompt label. */ + $('#projectKeyLabel').remove(); }) var scheduleOption = ""; @@ -169,6 +256,7 @@ $(document).ready(function() $('#engine').change(); $('#jkServer').change(); - + $('#frame').change(); + $('#sonarqubeServer').change(); $('#triggerType').change(); }); diff --git a/module/job/lang/en.php b/module/job/lang/en.php index f2353087d6..e8b58344a5 100644 --- a/module/job/lang/en.php +++ b/module/job/lang/en.php @@ -14,46 +14,52 @@ $lang->job->execSuccess = 'Build success'; $lang->job->browseAction = 'Job List'; -$lang->job->id = 'ID'; -$lang->job->name = 'Name'; -$lang->job->repo = 'Repo'; -$lang->job->product = $lang->productCommon; -$lang->job->svnDir = 'SVN Tag Watch Path'; -$lang->job->jenkins = 'Jenkins'; -$lang->job->jkHost = 'Jenkins Server'; -$lang->job->jkJob = 'Jenkins Task'; -$lang->job->buildSpec = 'Build Target'; // 'pipeline@server' -$lang->job->engine = 'Engine'; -$lang->job->server = 'Server'; -$lang->job->pipeline = 'Pipeline'; -$lang->job->buildType = 'Build Type'; -$lang->job->frame = 'Frame'; -$lang->job->triggerType = 'Trigger'; -$lang->job->atDay = 'Custom Days'; -$lang->job->atTime = 'At Time'; -$lang->job->lastStatus = 'Last Status'; -$lang->job->lastExec = 'Last Executed'; -$lang->job->comment = 'Match Keywords'; -$lang->job->customParam = 'Custom build parameters'; -$lang->job->paramName = 'Name'; -$lang->job->paramValue = 'Value'; -$lang->job->custom = 'Custom'; -$lang->job->createdBy = 'Created By'; -$lang->job->createdDate = 'Created Date'; -$lang->job->editedBy = 'Edited By'; -$lang->job->editedDate = 'Edited Date'; -$lang->job->lastTag = 'Last Tag'; -$lang->job->deleted = 'Deleted'; -$lang->job->repoServer = 'Repo Server'; +$lang->job->id = 'ID'; +$lang->job->name = 'Name'; +$lang->job->repo = 'Repo'; +$lang->job->product = $lang->productCommon; +$lang->job->svnDir = 'SVN Tag Watch Path'; +$lang->job->jenkins = 'Jenkins'; +$lang->job->jkHost = 'Jenkins Server'; +$lang->job->jkJob = 'Jenkins Task'; +$lang->job->buildSpec = 'Build Target'; // 'pipeline@server' +$lang->job->engine = 'Engine'; +$lang->job->server = 'Server'; +$lang->job->pipeline = 'Pipeline'; +$lang->job->buildType = 'Build Type'; +$lang->job->frame = 'Frame'; +$lang->job->triggerType = 'Trigger'; +$lang->job->atDay = 'Custom Days'; +$lang->job->atTime = 'At Time'; +$lang->job->lastStatus = 'Last Status'; +$lang->job->lastExec = 'Last Executed'; +$lang->job->comment = 'Match Keywords'; +$lang->job->customParam = 'Custom build parameters'; +$lang->job->paramName = 'Name'; +$lang->job->paramValue = 'Value'; +$lang->job->custom = 'Custom'; +$lang->job->createdBy = 'Created By'; +$lang->job->createdDate = 'Created Date'; +$lang->job->editedBy = 'Edited By'; +$lang->job->editedDate = 'Edited Date'; +$lang->job->lastTag = 'Last Tag'; +$lang->job->deleted = 'Deleted'; +$lang->job->repoServer = 'Repo Server'; +$lang->job->sonarqubeServer = 'SonarQube Server'; +$lang->job->projectKey = 'SonarQube Project'; $lang->job->lblBasic = 'Basic Info'; -$lang->job->example = 'e.g.'; -$lang->job->commitEx = "Used to match the keywords used to create a compile. Multiple keywords are separated by ','"; -$lang->job->cronSample = 'e.g. 0 0 2 * * 2-6/1 means 2:00 a.m. every weekday.'; -$lang->job->sendExec = 'Send execute request success.'; -$lang->job->inputName = 'Please enter parameter name.'; -$lang->job->invalidName = 'The parameter name should be letters, numbers or underlines.'; +$lang->job->example = 'e.g.'; +$lang->job->commitEx = "Used to match the keywords used to create a compile. Multiple keywords are separated by ','"; +$lang->job->cronSample = 'e.g. 0 0 2 * * 2-6/1 means 2:00 a.m. every weekday.'; +$lang->job->sendExec = 'Send execute request success.'; +$lang->job->inputName = 'Please enter parameter name.'; +$lang->job->invalidName = 'The parameter name should be letters, numbers or underlines.'; +$lang->job->repoExists = 'This repository has a build task associated with it『%s』'; +$lang->job->projectExists = 'This SonarQube Project has a build task associated with it『%s』'; +$lang->job->mustUseJenkins = 'SonarQube frame is only used if the build engine is JenKins.'; +$lang->job->jobIsDeleted = 'This repository is associated with a build task, please view the data from the recycle bin'; $lang->job->buildTypeList['build'] = 'Only Build'; $lang->job->buildTypeList['buildAndDeploy'] = 'Build And Deploy'; @@ -63,15 +69,16 @@ $lang->job->triggerTypeList['tag'] = 'Tag'; $lang->job->triggerTypeList['commit'] = 'Code Commit'; $lang->job->triggerTypeList['schedule'] = 'Schedule'; -$lang->job->frameList[''] = ''; -$lang->job->frameList['junit'] = 'JUnit'; -$lang->job->frameList['testng'] = 'TestNG'; -$lang->job->frameList['phpunit'] = 'PHPUnit'; -$lang->job->frameList['pytest'] = 'Pytest'; -$lang->job->frameList['jtest'] = 'JTest'; -$lang->job->frameList['cppunit'] = 'CppUnit'; -$lang->job->frameList['gtest'] = 'GTest'; -$lang->job->frameList['qtest'] = 'QTest'; +$lang->job->frameList[''] = ''; +$lang->job->frameList['junit'] = 'JUnit'; +$lang->job->frameList['testng'] = 'TestNG'; +$lang->job->frameList['phpunit'] = 'PHPUnit'; +$lang->job->frameList['pytest'] = 'Pytest'; +$lang->job->frameList['jtest'] = 'JTest'; +$lang->job->frameList['cppunit'] = 'CppUnit'; +$lang->job->frameList['gtest'] = 'GTest'; +$lang->job->frameList['qtest'] = 'QTest'; +$lang->job->frameList['sonarqube'] = 'SonarQube'; $lang->job->paramValueList[''] = ''; $lang->job->paramValueList['$zentao_version'] = 'Current version'; diff --git a/module/job/lang/zh-cn.php b/module/job/lang/zh-cn.php index 780ec84159..5f7bb63f04 100644 --- a/module/job/lang/zh-cn.php +++ b/module/job/lang/zh-cn.php @@ -14,46 +14,52 @@ $lang->job->execSuccess = '执行成功'; $lang->job->browseAction = '构建任务列表'; -$lang->job->id = 'ID'; -$lang->job->name = '名称'; -$lang->job->repo = '代码库'; -$lang->job->product = '关联' . $lang->productCommon; -$lang->job->svnDir = 'SVN监控路径'; -$lang->job->jenkins = 'Jenkins'; -$lang->job->jkHost = 'Jenkins服务器'; -$lang->job->jkJob = 'Jenkins任务'; -$lang->job->buildSpec = '构建对象'; // 'pipeline@server' -$lang->job->engine = '构建引擎'; -$lang->job->server = '服务器'; -$lang->job->pipeline = '流水线'; -$lang->job->buildType = '构建类型'; -$lang->job->frame = '工具/框架'; -$lang->job->triggerType = '触发方式'; -$lang->job->atDay = '自定义日期'; -$lang->job->atTime = '执行时间'; -$lang->job->lastStatus = '最后执行状态'; -$lang->job->lastExec = '最后执行时间'; -$lang->job->comment = '匹配关键字'; -$lang->job->customParam = '自定义构建参数'; -$lang->job->paramName = '名称'; -$lang->job->paramValue = '值'; -$lang->job->custom = '自定义'; -$lang->job->createdBy = '由谁创建'; -$lang->job->createdDate = '创建日期'; -$lang->job->editedBy = '由谁编辑'; -$lang->job->editedDate = '编辑日期'; -$lang->job->lastTag = '最后标签'; -$lang->job->deleted = '已删除'; -$lang->job->repoServer = '版本库服务器'; +$lang->job->id = 'ID'; +$lang->job->name = '名称'; +$lang->job->repo = '代码库'; +$lang->job->product = '关联' . $lang->productCommon; +$lang->job->svnDir = 'SVN监控路径'; +$lang->job->jenkins = 'Jenkins'; +$lang->job->jkHost = 'Jenkins服务器'; +$lang->job->jkJob = 'Jenkins任务'; +$lang->job->buildSpec = '构建对象'; // 'pipeline@server' +$lang->job->engine = '构建引擎'; +$lang->job->server = '服务器'; +$lang->job->pipeline = '流水线'; +$lang->job->buildType = '构建类型'; +$lang->job->frame = '工具/框架'; +$lang->job->triggerType = '触发方式'; +$lang->job->atDay = '自定义日期'; +$lang->job->atTime = '执行时间'; +$lang->job->lastStatus = '最后执行状态'; +$lang->job->lastExec = '最后执行时间'; +$lang->job->comment = '匹配关键字'; +$lang->job->customParam = '自定义构建参数'; +$lang->job->paramName = '名称'; +$lang->job->paramValue = '值'; +$lang->job->custom = '自定义'; +$lang->job->createdBy = '由谁创建'; +$lang->job->createdDate = '创建日期'; +$lang->job->editedBy = '由谁编辑'; +$lang->job->editedDate = '编辑日期'; +$lang->job->lastTag = '最后标签'; +$lang->job->deleted = '已删除'; +$lang->job->repoServer = '版本库服务器'; +$lang->job->sonarqubeServer = 'SonarQube服务器'; +$lang->job->projectKey = 'SonarQube项目'; $lang->job->lblBasic = '基本信息'; -$lang->job->example = '举例'; -$lang->job->commitEx = "用于匹配创建构建任务的关键字,多个关键字用','分割"; -$lang->job->cronSample = '如 0 0 2 * * 2-6/1 表示每个工作日凌晨2点'; -$lang->job->sendExec = '发送执行请求成功!执行结果:%s'; -$lang->job->inputName = '请输入参数名称。'; -$lang->job->invalidName = '参数名称应该是英文字母、数字或下划线的组合。'; +$lang->job->example = '举例'; +$lang->job->commitEx = "用于匹配创建构建任务的关键字,多个关键字用','分割"; +$lang->job->cronSample = '如 0 0 2 * * 2-6/1 表示每个工作日凌晨2点'; +$lang->job->sendExec = '发送执行请求成功!执行结果:%s'; +$lang->job->inputName = '请输入参数名称。'; +$lang->job->invalidName = '参数名称应该是英文字母、数字或下划线的组合。'; +$lang->job->repoExists = '此版本库已关联构建任务『%s』'; +$lang->job->projectExists = '此SonarQube项目已关联构建任务『%s』'; +$lang->job->mustUseJenkins = 'SonarQube工具/框架仅在构建引擎为JenKins的情况下使用'; +$lang->job->jobIsDeleted = '此版本库已关联构建任务,请从回收站查看数据'; $lang->job->buildTypeList['build'] = '仅构建'; $lang->job->buildTypeList['buildAndDeploy'] = '构建部署'; @@ -63,15 +69,16 @@ $lang->job->triggerTypeList['tag'] = '打标签'; $lang->job->triggerTypeList['commit'] = '提交注释包含关键字'; $lang->job->triggerTypeList['schedule'] = '定时计划'; -$lang->job->frameList[''] = ''; -$lang->job->frameList['junit'] = 'JUnit'; -$lang->job->frameList['testng'] = 'TestNG'; -$lang->job->frameList['phpunit'] = 'PHPUnit'; -$lang->job->frameList['pytest'] = 'Pytest'; -$lang->job->frameList['jtest'] = 'JTest'; -$lang->job->frameList['cppunit'] = 'CppUnit'; -$lang->job->frameList['gtest'] = 'GTest'; -$lang->job->frameList['qtest'] = 'QTest'; +$lang->job->frameList[''] = ''; +$lang->job->frameList['junit'] = 'JUnit'; +$lang->job->frameList['testng'] = 'TestNG'; +$lang->job->frameList['phpunit'] = 'PHPUnit'; +$lang->job->frameList['pytest'] = 'Pytest'; +$lang->job->frameList['jtest'] = 'JTest'; +$lang->job->frameList['cppunit'] = 'CppUnit'; +$lang->job->frameList['gtest'] = 'GTest'; +$lang->job->frameList['qtest'] = 'QTest'; +$lang->job->frameList['sonarqube'] = 'SonarQube'; $lang->job->paramValueList[''] = ''; $lang->job->paramValueList['$zentao_version'] = '当前版本号'; diff --git a/module/job/model.php b/module/job/model.php index 97673d40cc..d9b60f64b5 100644 --- a/module/job/model.php +++ b/module/job/model.php @@ -156,7 +156,8 @@ class jobModel extends model public function create() { $job = fixer::input('post') - ->setDefault('atDay', '') + ->setDefault('atDay,projectKey', '') + ->setDefault('sonarqubeServer', 0) ->add('createdBy', $this->app->user->account) ->add('createdDate', helper::now()) ->remove('repoType,reference') @@ -181,6 +182,35 @@ class jobModel extends model unset($job->jkTask); unset($job->gitlabRepo); + /* SonarQube tool is only used if the engine is JenKins. */ + if($job->engine != 'jenkins' and $job->frame == 'sonarqube') + { + dao::$errors[]['frame'] = $this->lang->job->mustUseJenkins; + return false; + } + + if($job->repo > 0 and $job->frame == 'sonarqube') + { + $sonarqubeJob = $this->getSonarqubeByRepo(array($job->repo)); + if(!empty($sonarqubeJob)) + { + $message = sprintf($this->lang->job->repoExists, $sonarqubeJob[$job->repo]->id . '-' . $sonarqubeJob[$job->repo]->name); + dao::$errors[]['repo'] = $message; + return false; + } + } + + if(!empty($job->projectKey) and $job->frame == 'sonarqube') + { + $projectList = $this->getJobBySonarqubeProject($job->sonarqubeServer, array($job->projectKey)); + if(!empty($projectList)) + { + $message = sprintf($this->lang->job->projectExists, $projectList[$job->projectKey]->id); + dao::$errors[]['projectKey'] = $message; + return false; + } + } + if($job->triggerType == 'schedule') $job->atDay = empty($_POST['atDay']) ? '' : join(',', $this->post->atDay); $job->svnDir = ''; @@ -219,6 +249,7 @@ class jobModel extends model ->batchCheckIF($job->triggerType === 'schedule', "atDay,atTime", 'notempty') ->batchCheckIF($job->triggerType === 'commit', "comment", 'notempty') ->batchCheckIF(($this->post->repoType == 'Subversion' and $job->triggerType == 'tag'), "svnDir", 'notempty') + ->batchCheckIF($job->frame === 'sonarqube', "sonarqubeServer,projectKey", 'notempty') ->autoCheck() ->exec(); if(dao::isError()) return false; @@ -268,6 +299,35 @@ class jobModel extends model unset($job->jkTask); unset($job->gitlabRepo); + /* SonarQube tool is only used if the engine is JenKins. */ + if($job->engine != 'jenkins' and $job->frame == 'sonarqube') + { + dao::$errors[] = $this->lang->job->mustUseJenkins; + return false; + } + + if($job->repo > 0 and $job->frame == 'sonarqube') + { + $sonarqubeJob = $this->getSonarqubeByRepo(array($job->repo), $id); + if(!empty($sonarqubeJob)) + { + $message = sprintf($this->lang->job->repoExists, $sonarqubeJob[$job->repo]->id . '-' . $sonarqubeJob[$job->repo]->name); + dao::$errors[]['repo'] = $message; + return false; + } + } + + if(!empty($job->projectKey) and $job->frame == 'sonarqube') + { + $projectList = $this->getJobBySonarqubeProject($job->sonarqubeServer, array($job->projectKey)); + if(!empty($projectList) && $projectList[$job->projectKey]->id != $id) + { + $message = sprintf($this->lang->job->projectExists, $projectList[$job->projectKey]->id); + dao::$errors[]['projectKey'] = $message; + return false; + } + } + if($job->triggerType == 'schedule') $job->atDay = empty($_POST['atDay']) ? '' : join(',', $this->post->atDay); $job->svnDir = ''; @@ -304,11 +364,10 @@ class jobModel extends model $this->dao->update(TABLE_JOB)->data($job) ->batchCheck($this->config->job->edit->requiredFields, 'notempty') - ->batchCheckIF($job->triggerType === 'schedule', "atDay,atTime", 'notempty') ->batchCheckIF($job->triggerType === 'commit', "comment", 'notempty') ->batchCheckIF(($this->post->repoType == 'Subversion' and $job->triggerType == 'tag'), "svnDir", 'notempty') - + ->batchCheckIF($job->frame === 'sonarqube', "sonarqubeServer,projectKey", 'notempty') ->autoCheck() ->where('id')->eq($id) ->exec(); @@ -539,4 +598,43 @@ class jobModel extends model return ''; } + + /** + * Get sonarqube by RepoID. + * + * @param array $repoIDList + * @param int $jobID + * @param bool $showDeleted + * @access public + * @return array + */ + public function getSonarqubeByRepo($repoIDList, $jobID = 0, $showDeleted = false) + { + return $this->dao->select('id,name,repo,deleted')->from(TABLE_JOB) + ->where('frame')->eq('sonarqube') + ->andWhere('repo')->in($repoIDList) + ->beginIF(!$showDeleted)->andWhere('deleted')->eq('0')->fi() + ->beginIF($jobID > 0)->andWhere('id')->ne($jobID)->fi() + ->fetchAll('repo'); + } + + /** + * Get job pairs by sonarqube projectkeys. + * + * @param int $sonarqubeID + * @param array $projectKeys + * @param bool $emptyShowAll + * @param bool $showDeleted + * @access public + * @return array + */ + public function getJobBySonarqubeProject($sonarqubeID, $projectKeys = array(), $emptyShowAll = false, $showDeleted = false) + { + return $this->dao->select('projectKey,id')->from(TABLE_JOB) + ->where('frame')->eq('sonarqube') + ->andWhere('sonarqubeServer')->eq($sonarqubeID) + ->beginIF(!$showDeleted)->andWhere('deleted')->eq('0')->fi() + ->beginIF(!empty($projectKeys) or !$emptyShowAll)->andWhere('projectKey')->in($projectKeys)->fi() + ->fetchPairs(); + } } diff --git a/module/job/view/create.html.php b/module/job/view/create.html.php index ac885915c0..9a3f95cdde 100644 --- a/module/job/view/create.html.php +++ b/module/job/view/create.html.php @@ -16,6 +16,7 @@ job->dirChange);?> job->buildTag);?> +job->frameList);?>
    @@ -26,7 +27,7 @@
    - + @@ -51,9 +52,13 @@ - + - + @@ -68,6 +73,20 @@ + + + + + + + + + + diff --git a/module/job/view/edit.html.php b/module/job/view/edit.html.php index 863e03f716..ade02dfa41 100644 --- a/module/job/view/edit.html.php +++ b/module/job/view/edit.html.php @@ -18,6 +18,8 @@ job->dirChange);?> job->buildTag);?> pipeline);?> + +job->frameList);?>
    @@ -28,7 +30,7 @@
    job->name; ?>job->name; ?>
    job->product; ?>
    job->frame; ?>job->frameList, '', "class='form-control chosen'"); ?> +
    + ''), '', "class='form-control chosen'"); ?> +
    +
    job->triggerType; ?>
    job->sonarqubeServer;?>
    job->projectKey;?> +
    + ''), '', "class='form-control chosen'");?> +
    +
    job->comment;?>
    - + @@ -50,9 +52,13 @@ - + - + @@ -88,6 +94,20 @@ + + + + + + + + + + diff --git a/module/kanban/view/space.html.php b/module/kanban/view/space.html.php index 1354a492e7..398f92e89c 100644 --- a/module/kanban/view/space.html.php +++ b/module/kanban/view/space.html.php @@ -44,7 +44,6 @@ kanban->closed;?>name;?> - kanbans) ? count($space->kanbans) : '';?> diff --git a/module/mr/model.php b/module/mr/model.php index 83863e4ebf..5565966381 100644 --- a/module/mr/model.php +++ b/module/mr/model.php @@ -258,6 +258,7 @@ class mrModel extends model { $MR = fixer::input('post') ->setDefault('jobID', 0) + ->setDefault('compileID', 0) ->setDefault('repoID', 0) ->setDefault('removeSourceBranch','0') ->setDefault('needCI', 0) diff --git a/module/mr/view/view.html.php b/module/mr/view/view.html.php index 1e7ba4295a..019476a3d1 100644 --- a/module/mr/view/view.html.php +++ b/module/mr/view/view.html.php @@ -133,7 +133,8 @@
    job->name;?>job->name;?> name, "class='form-control'");?>
    job->product;?> product, "class='form-control chosen'");?>
    job->frame;?>job->frameList, $job->frame, "class='form-control chosen'");?> +
    + ''), '', "class='form-control chosen'"); ?> +
    +
    job->triggerType;?>
    job->sonarqubeServer;?>sonarqubeServer, "class='form-control chosen' required");?>
    job->projectKey;?> +
    + ''), '', "class='form-control chosen'");?> +
    +
    job->comment;?> comment, "class='form-control'");?>
    - + app->getClientLang(), array('zh-cn','zh-tw')) ? 'w-90px' : 'w-100px'; ?> + diff --git a/module/my/view/dynamic.html.php b/module/my/view/dynamic.html.php index 5546dd3f2b..586b526f79 100644 --- a/module/my/view/dynamic.html.php +++ b/module/my/view/dynamic.html.php @@ -64,6 +64,10 @@ { echo '#' . $action->objectID; } + elseif(empty($action->objectID) and $action->extra) + { + echo $action->extra; + } elseif(empty($action->objectLink)) { echo $action->objectName; @@ -73,8 +77,10 @@ echo html::a($action->objectLink, $action->objectName, '', $tab); } ?> + objectID):?> objectID;?> + diff --git a/module/pipeline/model.php b/module/pipeline/model.php index 6fc6fde9d2..1e42120602 100644 --- a/module/pipeline/model.php +++ b/module/pipeline/model.php @@ -76,7 +76,7 @@ class pipelineModel extends model ->add('private',md5(rand(10,113450))) ->add('createdBy', $this->app->user->account) ->add('createdDate', helper::now()) - ->trim('token') + ->trim('url,token,account,password') ->skipSpecial('url,token,account,password') ->get(); if($type == 'gitlab') $pipeline->url = rtrim($pipeline->url, '/'); @@ -105,7 +105,7 @@ class pipelineModel extends model $pipeline = fixer::input('post') ->add('editedBy', $this->app->user->account) ->add('editedDate', helper::now()) - ->trim('token') + ->trim('url,token,account,password') ->skipSpecial('url,token,account,password') ->get(); @@ -122,4 +122,21 @@ class pipelineModel extends model return !dao::isError(); } + + /** + * Delete one record. + * + * @param string $id the id to be deleted + * @param string $object the action object + * @access public + * @return int + */ + public function delete($id, $object = 'gitlab') + { + $this->dao->update(TABLE_PIPELINE)->set('deleted')->eq(1)->where('id')->eq($id)->exec(); + $this->loadModel('action')->create($object, $id, 'deleted', '', ACTIONMODEL::CAN_UNDELETED); + + $actionID = $this->dao->lastInsertID(); + return $actionID; + } } diff --git a/module/repo/control.php b/module/repo/control.php index e1d3e0c407..1b601e3722 100644 --- a/module/repo/control.php +++ b/module/repo/control.php @@ -83,7 +83,8 @@ class repo extends control $repoID = $this->repo->saveState(0, $objectID); if($this->viewType !== 'json') $this->commonAction($repoID, $objectID); - $repoList = $this->repo->getList(0, '', $orderBy); + $repoList = $this->repo->getList(0, '', $orderBy); + $sonarRepoList = $this->loadModel('job')->getSonarqubeByRepo(array_keys($repoList)); /* Pager. */ $this->app->loadClass('pager', $static = true); @@ -95,11 +96,12 @@ class repo extends control $this->view->position[] = $this->lang->repo->common; $this->view->position[] = $this->lang->repo->browse; - $this->view->orderBy = $orderBy; - $this->view->objectID = $objectID; - $this->view->pager = $pager; - $this->view->repoList = empty($repoList) ? $repoList: $repoList[$pageID - 1];; - $this->view->products = $this->loadModel('product')->getPairs(); + $this->view->orderBy = $orderBy; + $this->view->objectID = $objectID; + $this->view->pager = $pager; + $this->view->repoList = empty($repoList) ? $repoList: $repoList[$pageID - 1];; + $this->view->products = $this->loadModel('product')->getPairs(); + $this->view->sonarRepoList = $sonarRepoList; $this->display(); } @@ -403,8 +405,6 @@ class repo extends control /* Cache infos. */ if($refresh or !$cacheFile or !file_exists($cacheFile) or (time() - filemtime($cacheFile)) / 60 > $this->config->repo->cacheTime) { - $this->repo->syncCommit($repoID, $branchID); - /* Get cache infos. */ $infos = $this->scm->ls($path, $revision); diff --git a/module/repo/model.php b/module/repo/model.php index d1302ee9a2..4a3245d85c 100644 --- a/module/repo/model.php +++ b/module/repo/model.php @@ -424,7 +424,7 @@ class repoModel extends model /* Convert to id by url. */ $this->loadModel('gitlab'); - $url = strtolower($url); + $url = str_replace('https://', 'http://', strtolower($url)); $matches = array(); foreach($gitlabs as $gitlabID => $gitlab) { @@ -435,7 +435,8 @@ class repoModel extends model $projects = $this->gitlab->apiGetProjects($gitlabID); foreach($projects as $project) { - if((!$isSSH and strtolower($project->http_url_to_repo) == $url) or ($isSSH and strtolower($project->ssh_url_to_repo) == $url)) + $urlToRepo = str_replace('https://', 'http://', strtolower($project->http_url_to_repo)); + if((!$isSSH and $urlToRepo == $url) or ($isSSH and strtolower($project->ssh_url_to_repo) == $url)) { $matched->gitlab = $gitlabID; $matched->project = $project->id; diff --git a/module/repo/view/maintain.html.php b/module/repo/view/maintain.html.php index f4f083953d..33ea5cdc56 100644 --- a/module/repo/view/maintain.html.php +++ b/module/repo/view/maintain.html.php @@ -27,7 +27,7 @@ - + @@ -59,6 +59,11 @@ common::printIcon('gitlab', 'createWebhook', "repoID=$repo->id", '', 'list', 'change', 'hiddenwin'); common::printIcon('gitlab', 'importIssue', "repo={$repo->id}", '', 'list', 'link'); } + if(isset($sonarRepoList[$repo->id])) + { + $jobID = $sonarRepoList[$repo->id]->id; + common::printIcon('sonarqube', 'execJob', "jobID=$jobID", '', 'list', 'sonarqube', 'hiddenwin'); + } common::printIcon('repo', 'delete', "repoID=$repo->id&objectID=$objectID", '', 'list', 'trash', 'hiddenwin'); ?> diff --git a/module/sonarqube/config.php b/module/sonarqube/config.php new file mode 100644 index 0000000000..9da271fc13 --- /dev/null +++ b/module/sonarqube/config.php @@ -0,0 +1,8 @@ +sonarqube = new stdclass(); + +$config->sonarqube->create = new stdclass(); +$config->sonarqube->create->requiredFields = 'name,url,account,password'; + +$config->sonarqube->edit = new stdclass(); +$config->sonarqube->edit->requiredFields = 'name,url,account,password'; diff --git a/module/sonarqube/control.php b/module/sonarqube/control.php new file mode 100644 index 0000000000..0750efef68 --- /dev/null +++ b/module/sonarqube/control.php @@ -0,0 +1,265 @@ + + * @package sonarqube + * @version $Id: ${FILE_NAME} 5144 2022/1/11 8:55 上午 caoyanyi@easycorp.ltd $ + * @link http://www.zentao.net + */ +class sonarqube extends control +{ + /** + * The mr constructor. + * @param string $moduleName + * @param string $methodName + */ + public function __construct($moduleName = '', $methodName = '') + { + parent::__construct($moduleName, $methodName); + + /* This is essential when changing tab(menu) from gitlab to repo. */ + /* Optional: common::setMenuVars('devops', $this->session->repoID); */ + $this->loadModel('ci')->setMenu(); + } + + /** + * Browse sonarqube. + * + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function browse($orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + + $sonarqubeList = $this->loadModel('pipeline')->getList('sonarqube', $orderBy, $pager); + + $this->view->title = $this->lang->sonarqube->common . $this->lang->colon . $this->lang->sonarqube->browse; + $this->view->sonarqubeList = $sonarqubeList; + $this->view->orderBy = $orderBy; + $this->view->pager = $pager; + + $this->display(); + } + + /** + * Ajax get project select. + * + * @param int $sonarqubeID + * @param string $projectKey + * @access public + * @return void + */ + public function ajaxGetProjectList($sonarqubeID, $projectKey = '') + { + $jobPairs = $this->loadModel('job')->getJobBySonarqubeProject($sonarqubeID, array(), true, true); + $existsProject = array_diff(array_keys($jobPairs), array($projectKey)); + + $projectList = $this->sonarqube->apiGetProjects($sonarqubeID); + + $projectPairs = array('' => ''); + foreach($projectList as $project) + { + if(!empty($project) and !in_array($project->key, $existsProject)) $projectPairs[$project->key] = $project->name; + } + + echo html::select('projectKey', $projectPairs, str_replace('*', '-', $projectKey), "class='form-control chosen'"); + } + + /** + * create a sonarqube. + * + * @access public + * @return void + */ + public function create() + { + if($_POST) + { + $this->checkToken(); + $sonarqubeID = $this->loadModel('pipeline')->create('sonarqube'); + + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + $actionID = $this->loadModel('action')->create('sonarqube', $sonarqubeID, 'created'); + return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse'))); + } + + $this->view->title = $this->lang->sonarqube->common . $this->lang->colon . $this->lang->sonarqube->createServer; + + $this->display(); + } + + /** + * Check post info. + * + * @param int $sonarqubeID + * @access protected + * @return void + */ + protected function checkToken($sonarqubeID = 0) + { + $sonarqube = fixer::input('post')->trim('url,token,account,password')->get(); + $this->dao->update('sonarqube')->data($sonarqube) + ->batchCheck(empty($sonarqubeID) ? $this->config->sonarqube->create->requiredFields : $this->config->sonarqube->edit->requiredFields, 'notempty') + ->batchCheck("url", 'URL'); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + if(strpos($sonarqube->url, 'http') !== 0) return $this->send(array('result' => 'fail', 'message' => array('url' => array($this->lang->sonarqube->hostError)))); + + /* Check name and url unique. */ + $existSonarQube = $this->dao->select('*')->from(TABLE_PIPELINE) + ->where("type='sonarqube' and (name='{$sonarqube->name}' or url='{$sonarqube->url}')") + ->andWhere('deleted')->eq('0') + ->beginIF(!empty($sonarqubeID))->andWhere('id')->ne($sonarqubeID)->fi() + ->fetch(); + if(isset($existSonarQube->name) and $existSonarQube->name == $sonarqube->name) return $this->send(array('result' => 'fail', 'message' => $this->lang->sonarqube->nameRepeatError)); + if(isset($existSonarQube->url) and $existSonarQube->url == $sonarqube->url) return $this->send(array('result' => 'fail', 'message' => $this->lang->sonarqube->urlRepeatError)); + + $token = base64_encode("{$sonarqube->account}:{$sonarqube->password}"); + $result = $this->sonarqube->apiValidate($sonarqube->url, $token); + + if(!isset($result->valid) or !$result->valid) return $this->send(array('result' => 'fail', 'message' => array('token' => array($this->lang->sonarqube->validError)))); + $this->post->set('token', $token); + } + + /** + * Edit a sonarqube. + * + * @param int $sonarqubeID + * @access public + * @return void + */ + public function edit($sonarqubeID) + { + $oldSonarQube = $this->loadModel('pipeline')->getByID($sonarqubeID); + + if($_POST) + { + $this->checkToken($sonarqubeID); + $this->pipeline->update($sonarqubeID); + $sonarqube = $this->pipeline->getByID($sonarqubeID); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + $this->loadModel('action'); + $actionID = $this->action->create('sonarqube', $sonarqubeID, 'edited'); + $changes = common::createChanges($oldSonarQube, $sonarqube); + $this->action->logHistory($actionID, $changes); + return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse'))); + } + + $this->view->title = $this->lang->sonarqube->common . $this->lang->colon . $this->lang->sonarqube->editServer; + $this->view->sonarqube = $oldSonarQube; + + $this->display(); + } + + /** + * Delete a sonarqube. + * + * @param int $sonarqubeID + * @access public + * @return void + */ + public function delete($sonarqubeID, $confirm = 'no') + { + if($confirm != 'yes') die(js::confirm($this->lang->sonarqube->confirmDelete, inlink('delete', "sonarqubeID=$sonarqubeID&confirm=yes"))); + + $oldSonarQube = $this->loadModel('pipeline')->getByID($sonarqubeID); + $this->loadModel('action'); + $actionID = $this->pipeline->delete($sonarqubeID, 'sonarqube'); + + $sonarQube = $this->pipeline->getByID($sonarqubeID); + $changes = common::createChanges($oldSonarQube, $sonarQube); + $this->action->logHistory($actionID, $changes); + echo js::reload('parent'); + } + + /** + * Exec job. + * + * @param int $jobID + * @access public + * @return void + */ + public function execJob($jobID) + { + echo $this->fetch('job', 'exec', "jobID=$jobID"); + } + + /** + * Browse sonarqube project. + * + * @param int $sonarqubeID + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function browseProject($sonarqubeID, $orderBy = 'name_desc', $recTotal = 0, $recPerPage = 15, $pageID = 1) + { + $this->app->loadClass('pager', $static = true); + $keyword = fixer::input('post')->setDefault('keyword', '')->get('keyword'); + + $sonarqubeProjectList = $this->sonarqube->apiGetProjects($sonarqubeID, $keyword); + $projectKeyList = array(); + foreach($sonarqubeProjectList as $key => $sonarqubeProject) + { + if(!isset($sonarqubeProject->lastAnalysisDate)) $sonarqubeProject->lastAnalysisDate = ''; + $projectKeyList[] = $sonarqubeProject->key; + } + + /* Data sort. */ + list($order, $sort) = explode('_', $orderBy); + $orderList = array(); + foreach($sonarqubeProjectList as $sonarqubeProject) $orderList[] = $sonarqubeProject->$order; + array_multisort($orderList, $sort == 'desc' ? SORT_DESC : SORT_ASC, $sonarqubeProjectList); + + /* Pager. */ + $this->app->loadClass('pager', $static = true); + $recTotal = count($sonarqubeProjectList); + $pager = new pager($recTotal, $recPerPage, $pageID); + $sonarqubeProjectList = array_chunk($sonarqubeProjectList, $pager->recPerPage); + + $this->view->sonarqube = $this->loadModel('pipeline')->getByID($sonarqubeID); + $this->view->keyword = urldecode(urldecode($keyword)); + $this->view->pager = $pager; + $this->view->title = $this->lang->sonarqube->common . $this->lang->colon . $this->lang->sonarqube->browseProject; + $this->view->sonarqubeID = $sonarqubeID; + $this->view->sonarqubeProjectList = (empty($sonarqubeProjectList) or empty($sonarqubeProjectList[$pageID - 1])) ? array() : $sonarqubeProjectList[$pageID - 1]; + $this->view->projectJobPairs = $this->loadModel('job')->getJobBySonarqubeProject($sonarqubeID, $projectKeyList); + $this->view->orderBy = $orderBy; + $this->display(); + } + + /** + * Delete project. + * + * @param int $sonarqubeID + * @param string $projectKey + * @param string $confirm + * @access public + * @return void + */ + public function deleteProject($sonarqubeID, $projectKey, $confirm = 'no') + { + if($confirm != 'yes') die(js::confirm($this->lang->sonarqube->confirmDeleteProject, inlink('deleteProject', "sonarqubeID=$sonarqubeID&projectKey=$projectKey&confirm=yes"))); + + /* Fix error when request type is PATH_INFO and the tag name contains '-'.*/ + $projectKey = str_replace('*', '-', $projectKey); + $reponse = $this->sonarqube->apiDeleteProject($sonarqubeID, $projectKey); + + if(isset($reponse->errors)) return print(js::alert($reponse->errors[0]->msg)); + + $this->loadModel('action')->create('sonarqubeproject', 0, 'deleted', '', $projectKey); + return print(js::reload('parent')); + } +} diff --git a/module/sonarqube/css/browseproject.css b/module/sonarqube/css/browseproject.css new file mode 100644 index 0000000000..d86ca85e6a --- /dev/null +++ b/module/sonarqube/css/browseproject.css @@ -0,0 +1,2 @@ +.text-c-counts > span {margin-left: 5px;} +#mainMenu .pull-left {margin-right: 15px;} diff --git a/module/sonarqube/css/create.css b/module/sonarqube/css/create.css new file mode 100644 index 0000000000..22cf991723 --- /dev/null +++ b/module/sonarqube/css/create.css @@ -0,0 +1,2 @@ +.table-form .c-name {width: 130px;} +.table-form .c-input {width: 650px;} diff --git a/module/sonarqube/css/edit.css b/module/sonarqube/css/edit.css new file mode 100644 index 0000000000..22cf991723 --- /dev/null +++ b/module/sonarqube/css/edit.css @@ -0,0 +1,2 @@ +.table-form .c-name {width: 130px;} +.table-form .c-input {width: 650px;} diff --git a/module/sonarqube/js/browseproject.js b/module/sonarqube/js/browseproject.js new file mode 100644 index 0000000000..edf62386c8 --- /dev/null +++ b/module/sonarqube/js/browseproject.js @@ -0,0 +1,22 @@ +$(document).ready(function() +{ + $('#projectSearch').click(function() + { + triggerSearch(); + }); + $('#keyword').keypress(function(event) + { + if(event.which == 13) triggerSearch(); + }) +}); + +/** + * Trigger filtering function. + * + * @access public + * @return void + */ +function triggerSearch() +{ + $("#sonarqubeProjectForm").submit(); +} diff --git a/module/sonarqube/lang/en.php b/module/sonarqube/lang/en.php new file mode 100644 index 0000000000..40e678bd27 --- /dev/null +++ b/module/sonarqube/lang/en.php @@ -0,0 +1,43 @@ +sonarqube = new stdclass; +$lang->sonarqube->common = 'SonarQube'; +$lang->sonarqube->browse = 'SonarQube Browse'; +$lang->sonarqube->search = 'Search'; +$lang->sonarqube->create = 'Create SonarQube'; +$lang->sonarqube->edit = 'Edit SonarQube'; +$lang->sonarqube->delete = 'Delete SonarQube'; +$lang->sonarqube->serverFail = 'Connect to SonarQube server failed, please check the SonarQube server.'; +$lang->sonarqube->browseProject = "SonarQube Project List"; +$lang->sonarqube->createProject = "Create SonarQube Project"; +$lang->sonarqube->deleteProject = "Delete SonarQube Project"; +$lang->sonarqube->placeholderSearch = 'Project name'; +$lang->sonarqube->execJob = "Exec SonarQube Job"; +$lang->sonarqube->desc = 'Description'; + +$lang->sonarqube->id = 'ID'; +$lang->sonarqube->name = "Server Name"; +$lang->sonarqube->url = 'Server Address'; +$lang->sonarqube->account = 'Username'; +$lang->sonarqube->password = 'Password'; +$lang->sonarqube->token = 'Token'; +$lang->sonarqube->defaultProject = 'Default Project'; +$lang->sonarqube->private = 'MD5 Verify'; + +$lang->sonarqube->createServer = 'Create SonarQube Server'; +$lang->sonarqube->editServer = 'Edit SonarQube Server'; +$lang->sonarqube->createSuccess = 'Create success'; + +$lang->sonarqube->placeholder = new stdclass; +$lang->sonarqube->placeholder->name = ''; +$lang->sonarqube->placeholder->url = "Please fill in the access address of the SonarQube Server homepage, as: https://sonarqube.zentao.net."; + +$lang->sonarqube->nameRepeatError = "Server name already exists!"; +$lang->sonarqube->urlRepeatError = 'Server address already exists!'; +$lang->sonarqube->validError = 'SonarQube user authority authentication failed!'; +$lang->sonarqube->hostError = "Invalid SonarQube service address."; +$lang->sonarqube->confirmDelete = 'Do you want to delete this SonarQube server?'; +$lang->sonarqube->confirmDeleteProject = 'Do you want to delete this SonarQube project?'; + +$lang->sonarqube->projectKey = 'Project Key'; +$lang->sonarqube->projectName = 'Project Name'; +$lang->sonarqube->projectlastAnalysis = 'Last analysis time'; diff --git a/module/sonarqube/lang/zh-cn.php b/module/sonarqube/lang/zh-cn.php new file mode 100644 index 0000000000..5a82b976d1 --- /dev/null +++ b/module/sonarqube/lang/zh-cn.php @@ -0,0 +1,43 @@ +sonarqube = new stdclass; +$lang->sonarqube->common = 'SonarQube'; +$lang->sonarqube->browse = '浏览SonarQube'; +$lang->sonarqube->search = '搜索'; +$lang->sonarqube->create = '添加SonarQube'; +$lang->sonarqube->edit = '编辑SonarQube'; +$lang->sonarqube->delete = '删除SonarQube'; +$lang->sonarqube->serverFail = '连接SonarQube服务器异常,请检查SonarQube服务器。'; +$lang->sonarqube->browseProject = "SonarQube项目列表"; +$lang->sonarqube->createProject = "创建SonarQube项目"; +$lang->sonarqube->deleteProject = "删除SonarQube项目"; +$lang->sonarqube->placeholderSearch = '请输入项目名称'; +$lang->sonarqube->execJob = "执行SonarQube任务"; + +$lang->sonarqube->id = 'ID'; +$lang->sonarqube->name = "服务器名称"; +$lang->sonarqube->url = '服务器地址'; +$lang->sonarqube->account = '用户名'; +$lang->sonarqube->password = '密码'; +$lang->sonarqube->token = 'Token'; +$lang->sonarqube->defaultProject = '默认项目'; +$lang->sonarqube->private = 'MD5验证'; + +$lang->sonarqube->createServer = '添加SonarQube服务器'; +$lang->sonarqube->editServer = '修改SonarQube服务器'; +$lang->sonarqube->desc = '描述'; +$lang->sonarqube->createSuccess = "创建成功"; + +$lang->sonarqube->placeholder = new stdclass; +$lang->sonarqube->placeholder->name = ''; +$lang->sonarqube->placeholder->url = "请填写SonarQube Server首页的访问地址,如:https://sonarqube.zentao.net。"; + +$lang->sonarqube->nameRepeatError = "服务器名称已存在!"; +$lang->sonarqube->urlRepeatError = "服务器地址已存在!"; +$lang->sonarqube->validError = "SonarQube 用户权限认证失败!"; +$lang->sonarqube->hostError = "无效的SonarQube服务地址。"; +$lang->sonarqube->confirmDelete = '确认删除该SonarQube吗?'; +$lang->sonarqube->confirmDeleteProject = '确认删除该SonarQube项目吗?'; + +$lang->sonarqube->projectKey = '项目标识'; +$lang->sonarqube->projectName = '项目名称'; +$lang->sonarqube->projectlastAnalysis = '最后执行时间'; diff --git a/module/sonarqube/model.php b/module/sonarqube/model.php new file mode 100644 index 0000000000..04b0a1012d --- /dev/null +++ b/module/sonarqube/model.php @@ -0,0 +1,92 @@ + + * @package sonarqube + * @version $Id: $ + * @link http://www.zentao.net + */ + +class sonarqubeModel extends model +{ + /** + * Get sonarqube api base url and header by id. + * + * @param int $sonarqubeID + * @access public + * @return array + */ + public function getApiBase($sonarqubeID) + { + $sonarqube = $this->loadModel('pipeline')->getByID($sonarqubeID); + if(!$sonarqube) return array('', array()); + + $url = rtrim($sonarqube->url, '/') . '/api/%s'; + $header[] = 'Authorization: Basic ' . $sonarqube->token; + + return array($url, $header); + } + + /** + * check sonarqube valid + * + * @param string $host + * @param string $token + * @access public + * @return array + */ + public function apiValidate($host, $token) + { + $url = rtrim($host, '/') . "/api/authentication/validate"; + $header = 'Authorization: Basic ' . $token; + return json_decode(commonModel::http($url, null, array(), $header)); + } + + /** + * Get projects of one sonarqube. + * + * @param int $sonarqubeID + * @param string $keyword + * @access public + * @return array + */ + public function apiGetProjects($sonarqubeID, $keyword = '') + { + list($apiRoot, $header) = $this->getApiBase($sonarqubeID); + if(!$apiRoot) return array(); + + $url = sprintf($apiRoot, "projects/search"); + + $allResults = array(); + for($page = 1; true; $page++) + { + $result = json_decode(commonModel::http($url. "?p={$page}&ps=500" . ($keyword ? "&q={$keyword}" : ''), null, array(), $header)); + if(!isset($result->components)) break; + if(!empty($result->components)) $allResults = array_merge($allResults, $result->components); + if(count($result->components) < 500) break; + } + + return $allResults; + } + + /** + * Delete sonarqube project by api. + * + * @param int $sonarqubeID + * @param int $projectKey + * @access public + * @return bool|object + */ + public function apiDeleteProject($sonarqubeID, $projectKey) + { + list($apiRoot, $header) = $this->getApiBase($sonarqubeID); + if(!$apiRoot) return false; + + $url = sprintf($apiRoot, "projects/delete?project=$projectKey"); + $result = json_decode(commonModel::http($url, null, array(CURLOPT_CUSTOMREQUEST => 'POST'), $header)); + return $result; + } +} diff --git a/module/sonarqube/view/browse.html.php b/module/sonarqube/view/browse.html.php new file mode 100644 index 0000000000..c41334e3be --- /dev/null +++ b/module/sonarqube/view/browse.html.php @@ -0,0 +1,64 @@ + + * @package sonarqube + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
    +

    + noData;?> + + createLink('sonarqube', 'create'), " " . $lang->sonarqube->create, '', "class='btn btn-info'");?> + +

    +
    + +
    + +
    job->common;?>job->common;?> name;?>
    repo->name); ?> repo->product); ?> repo->path; ?>actions; ?>actions; ?>
    + + + recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}";?> + + + + + + + + $sonarqube): ?> + + + + + + + + +
    sonarqube->id);?>sonarqube->name);?>sonarqube->url);?>actions;?>
    name;?>url;?> + ", '',"title='{$lang->sonarqube->browseProject}' class='btn btn-primary'"); + common::printLink('sonarqube', 'edit', "sonarqubeID=$id", " ", '',"title='{$lang->sonarqube->edit}' class='btn btn-primary'"); + if(common::hasPriv('sonarqube', 'delete')) echo html::a($this->createLink('sonarqube', 'delete', "sonarqubeID=$id"), '', 'hiddenwin', "title='{$lang->sonarqube->delete}' class='btn'"); + ?> +
    + + + +
    +
    + + diff --git a/module/sonarqube/view/browseproject.html.php b/module/sonarqube/view/browseproject.html.php new file mode 100644 index 0000000000..f068913912 --- /dev/null +++ b/module/sonarqube/view/browseproject.html.php @@ -0,0 +1,83 @@ + + * @package sonarqube + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
    +

    + noData;?> + + createLink('sonarqube', 'createProject', "sonarqubeID=$sonarqubeID"), " " . $lang->sonarqube->createProject, '', "class='btn btn-info'");?> + +

    +
    + +
    +
    + + recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}";?> + + + + + + + + + + $sonarqubeProject): ?> + + + + + + + + +
    sonarqube->projectKey);?>sonarqube->projectName);?>sonarqube->projectlastAnalysis);?>actions;?>
    key;?>name;?>lastAnalysisDate, 0, 10);?> + key); + common::printLink('sonarqube', 'deleteProject', "sonarqubeID=$sonarqubeID&project=$projectKey", " ", 'hiddenwin', "title='{$lang->sonarqube->deleteProject}' class='btn btn-primary'"); + if(isset($projectJobPairs[$sonarqubeProject->key])) + { + $jobID = $projectJobPairs[$sonarqubeProject->key]; + common::printIcon('sonarqube', 'execJob', "jobID=$jobID", '', 'list', 'sonarqube', 'hiddenwin'); + } + ?> +
    + + + +
    +
    + + diff --git a/module/sonarqube/view/create.html.php b/module/sonarqube/view/create.html.php new file mode 100644 index 0000000000..2da3a05837 --- /dev/null +++ b/module/sonarqube/view/create.html.php @@ -0,0 +1,51 @@ + + * @package sonarqube + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
    +
    +
    +
    +

    sonarqube->createServer;?>

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + +
    sonarqube->name;?>sonarqube->placeholder->name}'");?>
    sonarqube->url;?>sonarqube->placeholder->url}'");?>
    sonarqube->account;?>
    sonarqube->password;?>
    + + goback, '', 'class="btn btn-wide"');?> +
    +
    +
    +
    +
    + diff --git a/module/sonarqube/view/edit.html.php b/module/sonarqube/view/edit.html.php new file mode 100644 index 0000000000..d35df609eb --- /dev/null +++ b/module/sonarqube/view/edit.html.php @@ -0,0 +1,51 @@ + + * @package sonarqube + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
    +
    +
    +
    +

    sonarqube->editServer;?>

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + +
    sonarqube->name;?>name) ? $sonarqube->name : '', "class='form-control' placeholder='{$lang->sonarqube->placeholder->name}'");?>
    sonarqube->url;?>url) ? $sonarqube->url : '', "class='form-control' placeholder='{$lang->sonarqube->placeholder->url}'");?>
    sonarqube->account;?>account) ? $sonarqube->account : '', "class='form-control'");?>
    sonarqube->password;?>password) ? $sonarqube->password : '', "class='form-control'");?>
    + + goback, '', 'class="btn btn-wide"');?> +
    +
    +
    +
    +
    + diff --git a/test/data/pipeline.yaml b/test/data/pipeline.yaml index 1f79677b14..60ad7e0b48 100644 --- a/test/data/pipeline.yaml +++ b/test/data/pipeline.yaml @@ -3,24 +3,24 @@ author: Zeng gang version: "1.0" fields: - field: id - range: 1 + range: 1-2 - field: type - range: gitlab + range: gitlab,sonarqube - field: name - range: gitlab服务器 + range: gitlab服务器,sonarqube服务器 - field: url - range: [http://192.168.1.161:51080] + range: [http://192.168.1.161:51080],[http://192.168.1.161:59001] - field: account - range: [] + range: [],[admin] - field: password - range: [] + range: [],[UDJzc3cwcmQ=] - field: token - range: x88fZokrp5hShia2jyBN + range: x88fZokrp5hShia2jyBN,YWRtaW46UDJzc3cwcmQ= - field: private - range: 08bcc98f75d7d40053dc80722bdc117b + range: 08bcc98f75d7d40053dc80722bdc117b,228b25587479f2fc7570428e8bcbabdc - field: createdBy range: admin - field: createdDate - range: [2021-12-09 11:23:35] + range: [2021-12-09 11:23:35],[2022-01-12 08:47:37] - field: deleted range: 0 diff --git a/test/data/zentao/config.php b/test/data/zentao/config.php index e2cf2f6f82..e122bb76fc 100644 --- a/test/data/zentao/config.php +++ b/test/data/zentao/config.php @@ -20,6 +20,6 @@ $builder->team = array('rows' => 400, 'extends' => array('team')); $builder->build = array('rows' => 8, 'extends' => array('build')); $builder->release = array('rows' => 8, 'extends' => array('release')); -$builder->pipeline = array('rows' => 1, 'extends' => array('pipeline')); +$builder->pipeline = array('rows' => 2, 'extends' => array('pipeline')); $builder->repo = array('rows' => 1, 'extends' => array('repo')); $builder->mr = array('rows' => 1, 'extends' => array('mr')); diff --git a/test/model/gitlab/apicreatebranch.php b/test/model/gitlab/apicreatebranch.php index 3d42a3d7a0..2136ed0726 100755 --- a/test/model/gitlab/apicreatebranch.php +++ b/test/model/gitlab/apicreatebranch.php @@ -11,7 +11,7 @@ pid=1 使用空的gitlabID、projectID、分支对象创建GitLab分支 >> return false 使用空的gitlabID、projectID,正确的分支对象创建GitLab分支 >> return null 使用正确的gitlabID、分支信息,错误的projectID创建分支 >> 404 Project Not Found -使用正确的gitlabID,projectID,分支对象创建GitLab分支 >> test_branch17 +使用正确的gitlabID,projectID,分支对象创建GitLab分支 >> 1 使用重复的分支信息创建分支 >> Branch already exists */ @@ -36,5 +36,8 @@ $gitlabID = 1; r($gitlab->apiCreateBranch($gitlabID, $projectID, $branch)) && p('message') && e('404 Project Not Found'); //使用正确的gitlabID、分支信息,错误的projectID创建分支 $projectID = 1555; -r($gitlab->apiCreateBranch($gitlabID, $projectID, $branch)) && p('name') && e($branch->branch); //通过gitlabID,projectID,分支对象正确创建GitLab分支 +$result = $gitlab->apiCreateBranch($gitlabID, $projectID, $branch); +if(!empty($result->name) and $result->name == $branch->branch) $result = true; +if(!empty($result->message) and $result->message == 'Branch already exists') $result = true; +r($result) && p() && e(1); //通过gitlabID,projectID,分支对象正确创建GitLab分支 r($gitlab->apiCreateBranch($gitlabID, $projectID, $branch)) && p('message') && e('Branch already exists'); //使用重复的分支信息创建分支 diff --git a/test/model/gitlab/apicreatebranchpriv.php b/test/model/gitlab/apicreatebranchpriv.php index 5199077991..13f298a261 100755 --- a/test/model/gitlab/apicreatebranchpriv.php +++ b/test/model/gitlab/apicreatebranchpriv.php @@ -40,6 +40,7 @@ if($result === false) $result = 'return false'; r($result) && p() && e('return false'); //使用正确的gitlabID、保护分支信息,错误的projectID创建保护分支 $projectID = 966; +$gitlab->apiDeleteBranchPriv($gitlabID, $projectID, $branch->name); r($gitlab->apiCreateBranchPriv($gitlabID, $projectID, $branch)) && p('name') && e($branch->name); //通过gitlabID,projectID,保护分支对象正确创建GitLab保护分支 r($gitlab->apiCreateBranchPriv($gitlabID, $projectID, $branch)) && p('message') && e("Protected branch 'master' already exists"); //使用重复的保护分支信息创建保护分支 diff --git a/test/model/gitlab/apideletetagpriv.php b/test/model/gitlab/apideletetagpriv.php index 5454d31f5f..423bdc0dbc 100644 --- a/test/model/gitlab/apideletetagpriv.php +++ b/test/model/gitlab/apideletetagpriv.php @@ -35,7 +35,8 @@ $tag = 'masters'; $result = $gitlab->apiDeleteTagPriv($gitlabID, $projectID, $tag); r($result) && p('message') && e('404 Not found'); //使用错误的保护标签信息删除保护标签 -$tag = '2021/12/30'; -$result = $gitlab->apiDeleteTagPriv($gitlabID, $projectID, $tag); +$projectID = 1555; +$tag = 'test_tag1'; +$result = $gitlab->apiDeleteTagPriv($gitlabID, $projectID, $tag); if(!$result or substr($result->message, 0, 2) == '20') $result = 'return true'; r($result) && p() && e('return true'); //通过gitlabID,projectID,标签名称正确删除保护标签 diff --git a/test/model/gitlab/apigetbranches.php b/test/model/gitlab/apigetbranches.php index 643d88daa0..80621acb82 100755 --- a/test/model/gitlab/apigetbranches.php +++ b/test/model/gitlab/apigetbranches.php @@ -8,11 +8,11 @@ title=测试 gitlabModel::apiGetBranches(); cid=1 pid=1 -通过gitlabID,projectID,获取GitLab分支列表 >> branch1 -通过gitlabID,projectID,获取GitLab分支数量 >> 3 -当前项目没有分支时,获取GitLab分支列表 >> 0 +通过gitlabID,projectID,获取GitLab分支列表 >> 1 +通过gitlabID,projectID,获取GitLab分支数量 >> 1 +当前项目没有分支时,获取GitLab分支列表 >> 0 当gitlabID存在,projectID不存在时,获取GitLab分支列表 >> 404 Project Not Found -当gitlabID,projectID都为0时,获取GitLab分支列表 >> return empty +当gitlabID,projectID都为0时,获取GitLab分支列表 >> return empty */ @@ -21,11 +21,11 @@ $gitlab = $tester->loadModel('gitlab'); $gitlabID = 1; $projectID = 1552; $result = $gitlab->apiGetBranches($gitlabID, $projectID); -r($result) && p('0:name') && e('branch1'); //通过gitlabID,projectID,获取GitLab分支列表 -r(count($result)) && p() && e(3); //通过gitlabID,projectID,获取GitLab分支数量 +r(isset(array_shift($result)->name)) && p() && e(1); //通过gitlabID,projectID,获取GitLab分支列表 +r(count($result) > 0) && p() && e(1); //通过gitlabID,projectID,获取GitLab分支数量 $gitlabID = 1; -$projectID = 1570; +$projectID = 959; r(count($gitlab->apiGetBranches($gitlabID, $projectID))) && p() && e(0); //当前项目没有分支时,获取GitLab分支列表 $gitlabID = 1; diff --git a/test/model/gitlab/apigetsinglebranchpriv.php b/test/model/gitlab/apigetsinglebranchpriv.php index d9f2f4d12e..6ea3c8f5e1 100644 --- a/test/model/gitlab/apigetsinglebranchpriv.php +++ b/test/model/gitlab/apigetsinglebranchpriv.php @@ -10,8 +10,8 @@ pid=1 使用空的gitlabID、projectID、保护分支获取保护分支 >> return false 使用正确的gitlabID、保护分支信息,错误的projectID获取保护分支 >> 404 Project Not Found -使用正确的gitlabID,projectID,保护分支获取保护分支 >> master -使用错误的保护分支信息获取保护分支 >> 404 Not found +使用正确的gitlabID,projectID,保护分支获取保护分支 >> master +使用错误的保护分支信息获取保护分支 >> 404 Not found */ @@ -30,7 +30,7 @@ $branch = 'master'; $result = $gitlab->apiGetSingleBranchPriv($gitlabID, $projectID, $branch); r($result) && p('message') && e('404 Project Not Found'); //使用正确的gitlabID、保护分支信息,错误的projectID获取保护分支 -$projectID = 1565; +$projectID = 1552; $result = $gitlab->apiGetSingleBranchPriv($gitlabID, $projectID, $branch); r($result) && p('name') && e('master'); //通过gitlabID,projectID,分支名称正确获取保护分支 diff --git a/test/model/gitlab/apigettagprivs.php b/test/model/gitlab/apigettagprivs.php index 70fda76503..09b7e7b5ac 100644 --- a/test/model/gitlab/apigettagprivs.php +++ b/test/model/gitlab/apigettagprivs.php @@ -21,8 +21,8 @@ $gitlab = $tester->loadModel('gitlab'); $gitlabID = 1; $projectID = 1552; $result = $gitlab->apiGetTagPrivs($gitlabID, $projectID); -r(isset($result[0]->name)) && p() && e(1); //通过gitlabID,projectID,获取GitLab标签保护列表 -r(count($result) > 0) && p() && e(1); //通过gitlabID,projectID,获取GitLab标签保护数量 +r(isset(array_shift($result)->name)) && p() && e(1); //通过gitlabID,projectID,获取GitLab标签保护列表 +r(count($result) > 0) && p() && e(1); //通过gitlabID,projectID,获取GitLab标签保护数量 $gitlabID = 1; $projectID = 1570; diff --git a/test/model/gitlab/createbranch.php b/test/model/gitlab/createbranch.php index b89f6dab69..e12aabab11 100755 --- a/test/model/gitlab/createbranch.php +++ b/test/model/gitlab/createbranch.php @@ -40,7 +40,13 @@ r($result) && p() && e('return false'); //使用正确的gitlabID、分支信息 dao::$errors = array(); $projectID = 1555; -$result = $gitlab->createBranch($gitlabID, $projectID); + +/* Delete branches with the same name. */ +$apiRoot = $gitlab->getApiRoot($gitlabID); +$url = sprintf($apiRoot, "/projects/{$projectID}/repository/branches/{$_POST['branch']}"); +commonModel::http($url, array(), $options = array(CURLOPT_CUSTOMREQUEST => 'DELETE')); + +$result = $gitlab->createBranch($gitlabID, $projectID); if($result === true) $result = 'return true'; r($result) && p() && e('return true'); //通过gitlabID,projectID,分支对象正确创建GitLab分支 diff --git a/test/model/gitlab/createtag.php b/test/model/gitlab/createtag.php index 756dcdfa0a..f61022862c 100755 --- a/test/model/gitlab/createtag.php +++ b/test/model/gitlab/createtag.php @@ -40,7 +40,12 @@ r($result) && p() && e('return false'); //使用正确的gitlabID、标签信息 dao::$errors = array(); $projectID = 1555; -$result = $gitlab->createTag($gitlabID, $projectID); + +/* Delete tags with the same name. */ +$gitlab->apiDeleteTagPriv($gitlabID, $projectID, $_POST['tag_name']); +$gitlab->apiDeleteTag($gitlabID, $projectID, $_POST['tag_name']); + +$result = $gitlab->createTag($gitlabID, $projectID); if($result === true) $result = 'return true'; r($result) && p() && e('return true'); //通过gitlabID,projectID,标签对象正确创建GitLab标签 diff --git a/test/model/mr/apicreate.php b/test/model/mr/apicreate.php index 2a775f0a84..129f749a6d 100644 --- a/test/model/mr/apicreate.php +++ b/test/model/mr/apicreate.php @@ -10,7 +10,7 @@ pid=0 使用空的RepoUrl数据创建 >> fail 使用正确的RepoUrl,错误的分支数据创建mr请求 >> fail -使用源分支和目标分支一样的数据创建mr请求 >> success +使用源分支和目标分支一样的数据创建mr请求 >> success 使用正确的数据创建mr请求 >> success */ @@ -28,7 +28,7 @@ r($result) && p() && e('fail'); //使用空的RepoUrl数据创建 dao::$errors = array(); $_POST['data']['RepoUrl'] = 'http://192.168.1.161:51080/root/azalea723test.git'; -$_POST['data']['DiffMsg'] = ''; +$_POST['data']['DiffMsg'] = ''; $_POST['data']['RepoSrcBranch'] = ''; $_POST['data']['RepoDistBranch'] = ''; $_POST['data']['MergeStatus'] = '1'; diff --git a/test/model/sonarqube/apideleteproject.php b/test/model/sonarqube/apideleteproject.php new file mode 100644 index 0000000000..ddf5835ff4 --- /dev/null +++ b/test/model/sonarqube/apideleteproject.php @@ -0,0 +1,42 @@ +#!/usr/bin/env php +> return false +正确的sonarqubeID,空的projectKey,删除SonarQube项目 >> The 'project' parameter is missing +正确的sonarqubeID,不存在的projectKey,删除SonarQube项目 >> Project 'no_project' not found +正确的sonarqubeID、projectKey,删除SonarQube项目 >> return true + +*/ + +$sonarqube = $tester->loadModel('sonarqube'); + +$sonarqubeID = 0; +$projectKey = ''; +$result = $sonarqube->apiDeleteProject($sonarqubeID, $projectKey); +if($result === false) $result = 'return false'; +r($result) && p() && e('return false'); //通过不正确的sonarqubeID、projectKey,删除SonarQube项目 + +$sonarqubeID = 2; +$projectKey = ''; +$result = $sonarqube->apiDeleteProject($sonarqubeID, $projectKey); +r($result->errors) && p('0:msg') && e("The 'project' parameter is missing"); //正确的sonarqubeID,空的projectKey,删除SonarQube项目 + +$projectKey = 'no_project'; +$result = $sonarqube->apiDeleteProject($sonarqubeID, $projectKey); +r($result->errors) && p('0:msg') && e("Project 'no_project' not found"); //正确的sonarqubeID,不存在的projectKey,删除SonarQube项目 + +$projectKey = 'new_project'; +list($apiRoot, $header) = $sonarqube->getApiBase($sonarqubeID); +$url = sprintf($apiRoot, "projects/create?name=$projectKey&project=$projectKey"); +commonModel::http($url, null, array(CURLOPT_CUSTOMREQUEST => 'POST'), $header); + +$result = $sonarqube->apiDeleteProject($sonarqubeID, $projectKey); +if($result === null) $result = 'return true'; +r($result) && p() && e("return true"); //正确的sonarqubeID、projectKey,删除SonarQube项目 diff --git a/test/model/sonarqube/apigetprojects.php b/test/model/sonarqube/apigetprojects.php new file mode 100644 index 0000000000..77404afa4c --- /dev/null +++ b/test/model/sonarqube/apigetprojects.php @@ -0,0 +1,32 @@ +#!/usr/bin/env php +> 1 +通过sonarqubeID,获取SonarQube项目数量 >> 1 +通过sonarqubeID,关键字,搜索获取SonarQube项目 >> 1 +当sonarqubeID为0时,获取SonarQube项目列表 >> return empty + +*/ + +$sonarqube = $tester->loadModel('sonarqube'); + +$sonarqubeID = 2; +$result = $sonarqube->apiGetProjects($sonarqubeID); +r(isset($result[0]->name)) && p() && e(1); //通过sonarqubeID,获取SonarQube项目列表 +r(count($result) > 0) && p() && e(1); //通过sonarqubeID,获取SonarQube项目数量 + +$keyword = '02'; +$result = $sonarqube->apiGetProjects($sonarqubeID, $keyword); +r(strpos($result[0]->name, '02') !== false) && p() && e(1); //通过sonarqubeID,关键字,搜索获取SonarQube项目 + +$sonarqubeID = 0; +$result = $sonarqube->apiGetProjects($sonarqubeID); +if(empty($result)) $result = 'return empty'; +r($result) && p() && e('return empty'); //当sonarqubeID为0时,获取SonarQube项目列表 diff --git a/test/model/sonarqube/apivalidate.php b/test/model/sonarqube/apivalidate.php new file mode 100644 index 0000000000..e860b2d0c9 --- /dev/null +++ b/test/model/sonarqube/apivalidate.php @@ -0,0 +1,25 @@ +#!/usr/bin/env php +> 1 +通过正确的host,错误的token获取api权限 >> return false + +*/ + +$sonarqube = $tester->loadModel('sonarqube'); + +$sonarqubeID = 2; +$sonarqubeServer = $sonarqube->getByID($sonarqubeID); +$result = $sonarqube->apiValidate($sonarqubeServer->url, $sonarqubeServer->token); +r($result) && p('valid') && e(1); //通过host,token检验api权限 + +$result = $sonarqube->apiValidate($sonarqubeServer->url, $sonarqubeServer->token . 'a'); +if($result->valid == false) $result = 'return false'; +r($result) && p('') && e(''); //通过正确的host,错误的token获取api权限 diff --git a/test/model/sonarqube/getapibase.php b/test/model/sonarqube/getapibase.php new file mode 100644 index 0000000000..1bd28ff870 --- /dev/null +++ b/test/model/sonarqube/getapibase.php @@ -0,0 +1,25 @@ +#!/usr/bin/env php +> http://192.168.1.161:59001/api/%s +当sonarqubeID为0时,获取SonarQube url和header >> return empty + +*/ + +$sonarqube = $tester->loadModel('sonarqube'); + +$sonarqubeID = 2; +list($apiRoot, $header) = $sonarqube->getApiBase($sonarqubeID); +r($apiRoot) && p() && e('http://192.168.1.161:59001/api/%s'); //通过sonarqubeID,获取SonarQube url和header + +$sonarqubeID = 0; +list($apiRoot, $header) = $sonarqube->getApiBase($sonarqubeID); +if(empty($apiRoot)) $result = 'return empty'; +r($result) && p() && e('return empty'); //当sonarqubeID为0时,获取SonarQube url和header diff --git a/test/model/sonarqube/getbyid.php b/test/model/sonarqube/getbyid.php new file mode 100644 index 0000000000..de3b3deb55 --- /dev/null +++ b/test/model/sonarqube/getbyid.php @@ -0,0 +1,25 @@ +#!/usr/bin/env php +> sonarqube服务器 +通过空的id获取sonarqube服务器 >> return empty + +*/ + +$sonarqube = $tester->loadModel('sonarqube'); + +$sonarqubeID = 2; +$sonarqubeServer = $sonarqube->getByID($sonarqubeID); +r($sonarqubeServer) && p('name') && e('sonarqube服务器'); //通过id获取sonarqube服务器 + +$sonarqubeID = 0; +$sonarqubeServer = $sonarqube->getByID($sonarqubeID); +if(empty($sonarqubeServer)) $result = 'return empty'; +r($result) && p() && e('return empty'); //通过空的id获取sonarqube服务器