control.php 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  1. <?php
  2. /**
  3. * The control file of mr module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
  6. * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Yanyi Cao <caoyanyi@easycorp.ltd>
  8. * @package mr
  9. * @link https://www.zentao.net
  10. * @property mrModel $mr
  11. * @property mrZen $mrZen
  12. */
  13. class mr extends control
  14. {
  15. /**
  16. * The gitlab constructor.
  17. * @param string $moduleName
  18. * @param string $methodName
  19. */
  20. public function __construct($moduleName = '', $methodName = '')
  21. {
  22. parent::__construct($moduleName, $methodName);
  23. /* This is essential when changing tab(menu) from gitlab to repo. */
  24. /* Optional: common::setMenuVars('devops', $this->session->repoID); */
  25. if($this->app->getMethodName() != 'browse')
  26. {
  27. $this->loadModel('ci')->setMenu();
  28. $this->view->objectID = 0;
  29. if(in_array($this->app->tab, array('execution', 'project'))) $this->view->objectID = $this->session->{$this->app->tab};
  30. if($this->app->tab == 'execution')
  31. {
  32. $this->view->executionID = $this->session->execution;
  33. $this->loadModel('execution')->setMenu((int)$this->session->execution);
  34. }
  35. if($this->app->tab == 'project')
  36. {
  37. $this->view->projectID = $this->session->project;
  38. $this->loadModel('project')->setMenu((int)$this->session->project);
  39. }
  40. /* Unlock session for wait to get data of repo. */
  41. if(!in_array(strtolower($this->app->methodName), array('linkbug', 'linkstory', 'linktask'))) session_write_close();
  42. }
  43. }
  44. /**
  45. * 获取合并请求列表.
  46. * Browse mr.
  47. *
  48. * @param int $repoID
  49. * @param string $mode
  50. * @param string $param
  51. * @param int $objectID
  52. * @param string $orderBy
  53. * @param int $recTotal
  54. * @param int $recPerPage
  55. * @param int $pageID
  56. * @access public
  57. * @return void
  58. */
  59. public function browse($repoID = 0, $mode = 'status', $param = 'opened', $objectID = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  60. {
  61. if($this->app->tab == 'execution')
  62. {
  63. $this->session->set('execution', $objectID);
  64. $execution = $this->loadModel('execution')->getByID($objectID);
  65. if($execution && $execution->type === 'kanban') return $this->locate($this->createLink('execution', 'kanban', "executionID=$objectID"));
  66. $features = $this->execution->getExecutionFeatures($execution);
  67. if(!$features['devops']) return $this->locate($this->createLink('execution', 'task', "objectID=$objectID"));
  68. $this->loadModel('execution')->setMenu($objectID);
  69. }
  70. elseif($this->app->tab == 'project')
  71. {
  72. $this->session->set('project', $objectID);
  73. $this->loadModel('project')->setMenu($objectID);
  74. }
  75. if(in_array($this->app->tab, array('execution', 'project')) && $objectID) return print($this->fetch('mr', 'browseByExecution', "repoID={$repoID}&mode={$mode}&param={$param}&objectID={$objectID}&orderBy={$orderBy}&recTotal={$recTotal}&recPerPage={$recPerPage}&pageID={$pageID}"));
  76. session_write_close();
  77. $repoList = $this->loadModel('repo')->getListBySCM(implode(',', $this->config->repo->gitServiceTypeList));
  78. if(empty($repoList)) $this->locate($this->repo->createLink('create'));
  79. if(!$repoID) $repoID = key($repoList);
  80. $repoID = $this->repo->saveState($repoID, $objectID);
  81. if(!isset($repoList[$repoID])) return $this->locate($this->createLink('repo', 'browse', "repoID=$repoID&objectID=$objectID"));
  82. $repo = $repoList[$repoID];
  83. $this->loadModel('ci')->setMenu($repo->id);
  84. if($param == 'assignee' || $param == 'creator')
  85. {
  86. $mode = $param;
  87. $param = $this->app->user->account;
  88. }
  89. $this->app->loadClass('pager', true);
  90. $pager = new pager($recTotal, $recPerPage, $pageID);
  91. $filterProjects = empty($repo->serviceProject) ? array() : array($repo->serviceHost => $repo->serviceProject);
  92. $MRList = $this->mr->getList($mode, $param, $orderBy, $filterProjects, $repoID, 0, $pager);
  93. $projects = $this->mrZen->getAllProjects($repo);
  94. $this->view->title = $this->lang->mr->common . $this->lang->hyphen . $this->lang->mr->browse;
  95. $this->view->MRList = $MRList;
  96. $this->view->projects = $projects;
  97. $this->view->pager = $pager;
  98. $this->view->mode = $mode;
  99. $this->view->param = $param;
  100. $this->view->objectID = $objectID;
  101. $this->view->repo = $repo;
  102. $this->view->orderBy = $orderBy;
  103. $this->view->openIDList = $this->app->user->admin ? array() : $this->loadModel('pipeline')->getProviderPairsByAccount($repo->SCM);
  104. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  105. $this->display();
  106. }
  107. /**
  108. * 获取执行下合并请求列表.
  109. * Browse mr for execution.
  110. *
  111. * @param int $repoID
  112. * @param string $mode
  113. * @param string $param
  114. * @param int $projectID
  115. * @param string $orderBy
  116. * @param int $recTotal
  117. * @param int $recPerPage
  118. * @param int $pageID
  119. * @access public
  120. * @return void
  121. */
  122. public function browseByExecution($repoID = 0, $mode = 'status', $param = 'opened', $projectID = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  123. {
  124. if($param == 'assignee' || $param == 'creator')
  125. {
  126. $mode = $param;
  127. $param = $this->app->user->account;
  128. }
  129. $this->app->loadClass('pager', true);
  130. $pager = new pager($recTotal, $recPerPage, $pageID);
  131. $MRList = $this->mr->getList($mode, $param, $orderBy, array(), $repoID, $projectID, $pager);
  132. $repoList = $this->loadModel('repo')->getList($projectID);
  133. $repoPairs = array();
  134. foreach($repoList as $repo)
  135. {
  136. if(!in_array($repo->SCM, $this->config->repo->gitServiceTypeList)) continue;
  137. $repoPairs[$repo->id] = $repo->name;
  138. if($repoID && $repoID != $repo->id) continue;
  139. }
  140. $openIDList = array();
  141. if(!$this->app->user->admin)
  142. {
  143. $this->loadModel('pipeline');
  144. foreach(array('gitlab', 'gitea', 'gogs') as $service) $openIDList += $this->pipeline->getProviderPairsByAccount($service);
  145. }
  146. $objectName = $this->app->tab == 'project' ? 'projectID' : 'executionID';
  147. $this->view->{$objectName} = $projectID;
  148. $this->view->title = $this->lang->mr->common . $this->lang->hyphen . $this->lang->mr->browse;
  149. $this->view->MRList = $MRList;
  150. $this->view->pager = $pager;
  151. $this->view->mode = $mode;
  152. $this->view->repoID = $repoID;
  153. $this->view->param = $param;
  154. $this->view->objectID = $projectID;
  155. $this->view->repoList = $repoList;
  156. $this->view->repoPairs = $repoPairs;
  157. $this->view->orderBy = $orderBy;
  158. $this->view->openIDList = $openIDList;
  159. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  160. $this->display();
  161. }
  162. /**
  163. * 创建合并请求.
  164. * Create MR function.
  165. *
  166. * @param int $repoID
  167. * @param int $objectID
  168. * @access public
  169. * @return void
  170. */
  171. public function create($repoID = 0, $objectID = 0)
  172. {
  173. if($_POST)
  174. {
  175. $MR = form::data($this->config->mr->form->create)
  176. ->setIF($this->post->needCI == 0, 'jobID', 0)
  177. ->add('createdBy', $this->app->user->account)
  178. ->skipSpecial('title,description')
  179. ->get();
  180. $result = $this->mr->create($MR);
  181. return $this->send($result);
  182. }
  183. $repoID = $this->loadModel('repo')->saveState($repoID);
  184. $repo = $this->repo->getByID($repoID);
  185. if(!in_array($repo->SCM, $this->config->repo->gitServiceTypeList))
  186. {
  187. $repoList = $this->repo->getListBySCM(implode(',', $this->config->repo->gitServiceTypeList));
  188. $repoID = key($repoList);
  189. $repoID = $this->loadModel('repo')->saveState($repoID);
  190. $repo = $repoList[$repoID];
  191. }
  192. if(in_array($repo->SCM, $this->config->repo->notSyncSCM)) $repo->serviceProject = (int)$repo->serviceProject;
  193. $project = $this->loadModel(strtolower($repo->SCM))->apiGetSingleProject($repo->gitService, $repo->serviceProject, false);
  194. $jobPairs = array();
  195. $jobs = $this->loadModel('job')->getListByRepoID($repoID);
  196. foreach($jobs as $job) $jobPairs[$job->id] = "[{$job->id}]{$job->name}";
  197. $repoPairs = array();
  198. if(in_array($this->app->tab, array('execution', 'project')) && $objectID)
  199. {
  200. $repoList = $this->loadModel('repo')->getList($objectID);
  201. foreach($repoList as $repoInfo)
  202. {
  203. if(in_array($repoInfo->SCM, $this->config->repo->gitServiceTypeList)) $repoPairs[$repoInfo->id] = $repoInfo->name;
  204. }
  205. }
  206. $this->view->title = $this->lang->mr->create;
  207. $this->view->users = $this->loadModel('user')->getPairs('noletter|noclosed');
  208. $this->view->repo = $repo;
  209. $this->view->repoPairs = $repoPairs;
  210. $this->view->project = $project;
  211. $this->view->executionID = $objectID;
  212. $this->view->objectID = $objectID;
  213. $this->view->jobPairs = $jobPairs;
  214. $this->display();
  215. }
  216. /**
  217. * 编辑合并请求。
  218. * Edit MR function.
  219. *
  220. * @param int $MRID
  221. * @access public
  222. * @return void
  223. */
  224. public function edit($MRID)
  225. {
  226. if($_POST)
  227. {
  228. $MR = form::data($this->config->mr->form->edit)
  229. ->setIF($this->post->needCI == 0, 'jobID', 0)
  230. ->add('editedBy', $this->app->user->account)
  231. ->skipSpecial('title,description')
  232. ->get();
  233. $result = $this->mr->update($MRID, $MR);
  234. return $this->send($result);
  235. }
  236. $MR = $this->mr->fetchByID($MRID);
  237. if(isset($MR->hostID)) $rawMR = $this->mr->apiGetSingleMR($MR->repoID, $MR->mriid);
  238. $this->view->title = $this->lang->mr->edit;
  239. $this->view->MR = $MR;
  240. $this->view->rawMR = isset($rawMR) ? $rawMR : false;
  241. if(!isset($rawMR->id) || empty($rawMR)) return $this->display();
  242. /* Fetch user list both in Zentao and current GitLab project. */
  243. $host = $this->loadModel('pipeline')->getByID($MR->hostID);
  244. $this->view->host = $host;
  245. $this->mrZen->assignEditData($MR, $host->type);
  246. }
  247. /**
  248. * 删除合并请求。
  249. * Delete a MR.
  250. *
  251. * @param int $MRID
  252. * @access public
  253. * @return void
  254. */
  255. public function delete($MRID)
  256. {
  257. $mr = $this->mr->fetchByID($MRID);
  258. $this->mr->deleteByID($MRID);
  259. if(dao::isError()) return $this->sendError(dao::getError());
  260. return $this->sendSuccess(array('load' => $this->createLink($this->app->rawModule, 'browse', "repoID={$mr->repoID}")));
  261. }
  262. /**
  263. * 合并请求详情。
  264. * View a MR.
  265. *
  266. * @param int $MRID
  267. * @access public
  268. * @return void
  269. */
  270. public function view($MRID)
  271. {
  272. $oldMR = $this->mr->fetchByID($MRID);
  273. if(!$oldMR) return $this->locate($this->createLink($this->app->rawModule, 'browse'));
  274. if(isset($oldMR->hostID)) $rawMR = $this->mr->apiGetSingleMR($oldMR->repoID, $oldMR->mriid);
  275. if($oldMR->synced && (!isset($rawMR->id) || empty($rawMR))) $this->sendError($this->lang->mr->apiError->emptyResponse, true);
  276. /* Sync MR from GitLab to ZenTaoPMS. */
  277. $MR = $this->mr->apiSyncMR($oldMR);
  278. $host = $this->loadModel('pipeline')->getByID($MR->hostID);
  279. if(in_array($host->type, $this->config->pipeline->formatTypeService))
  280. {
  281. $MR->sourceProject = (int)$MR->sourceProject;
  282. $MR->targetProject = (int)$MR->targetProject;
  283. }
  284. $sourceProject = $this->loadModel($host->type)->apiGetSingleProject($MR->hostID, $MR->sourceProject, false);
  285. $compile = $this->loadModel('compile')->getByID($MR->compileID);
  286. $this->view->title = $this->lang->mr->view;
  287. $this->view->MR = $MR;
  288. $this->view->rawMR = isset($rawMR) ? $rawMR : false;
  289. $this->view->repo = $this->loadModel('repo')->getByID($MR->repoID);
  290. $this->view->reviewer = $this->loadModel('user')->getById($MR->assignee);
  291. $this->view->actions = $this->loadModel('action')->getList($this->app->rawModule, $MRID);
  292. $this->view->compile = $compile;
  293. $this->view->hasNewCommit = $compile ? $this->mrZen->checkNewCommit($host->type, $MR->hostID, (string)$MR->targetProject, $MR->mriid, $compile->createdDate) : false;
  294. $this->view->sourceProject = $sourceProject;
  295. $this->view->targetProject = $this->{$host->type}->apiGetSingleProject($MR->hostID, $MR->targetProject, false);
  296. $this->view->sourceBranch = $this->mrZen->getBranchUrl($host, $MR->sourceProject, $MR->sourceBranch);
  297. $this->view->targetBranch = $this->mrZen->getBranchUrl($host, $MR->targetProject, $MR->targetBranch);
  298. $this->display();
  299. }
  300. /**
  301. * 定时任务,从GitLab API同步合并请求状态到禅道数据库,默认5分钟执行一次。
  302. * Crontab sync MR from GitLab API to Zentao database, default time 5 minutes to execute once.
  303. *
  304. * @access public
  305. * @return void
  306. */
  307. public function syncMR()
  308. {
  309. $MRList = $this->mr->getList();
  310. $this->mr->batchSyncMR($MRList);
  311. if(dao::isError())
  312. {
  313. echo json_encode(dao::getError());
  314. return true;
  315. }
  316. echo 'success';
  317. }
  318. /**
  319. * 通过合并请求。
  320. * Accept a MR.
  321. *
  322. * @param int $MRID
  323. * @access public
  324. * @return void
  325. */
  326. public function accept($MRID)
  327. {
  328. $MR = $this->mr->fetchByID($MRID);
  329. /* Judge that if this MR can be accepted. */
  330. if(isset($MR->needCI) and $MR->needCI == '1')
  331. {
  332. $compileStatus = empty($MR->compileID) ? 'fail' : $this->loadModel('compile')->getByID($MR->compileID)->status;
  333. if(isset($compileStatus) and $compileStatus != 'success')
  334. {
  335. return $this->sendError($this->lang->mr->needCI);
  336. }
  337. }
  338. if(isset($MR->needApproved) and $MR->needApproved == '1')
  339. {
  340. if($MR->approvalStatus != 'approved')
  341. {
  342. return $this->sendError($this->lang->mr->needApproved);
  343. }
  344. }
  345. if(isset($MR->hostID)) $rawMR = $this->mr->apiAcceptMR($MR);
  346. if(isset($rawMR->state) and $rawMR->state == 'merged')
  347. {
  348. $this->mr->logMergedAction($MR);
  349. return $this->sendSuccess(array('message' => $this->lang->mr->mergeSuccess, 'load' => true));
  350. }
  351. /* The type of variable `$rawMR->message` is string. This is different with apiCreateMR. */
  352. if(isset($rawMR->message))
  353. {
  354. $errorMessage = $this->mr->convertApiError($rawMR->message);
  355. return $this->sendError(sprintf($this->lang->mr->apiError->sudo, $errorMessage));
  356. }
  357. return $this->sendError($this->lang->mr->mergeFailed);
  358. }
  359. /**
  360. * 获取合并请求的对比信息。
  361. * View diff between MR source and target branches.
  362. *
  363. * @param int $MRID
  364. * @access public
  365. * @return void
  366. * @param string $encoding
  367. */
  368. public function diff($MRID, $encoding = '')
  369. {
  370. $encoding = empty($encoding) ? 'utf-8' : $encoding;
  371. $encoding = strtolower(str_replace('_', '-', $encoding)); /* Revert $config->requestFix in $encoding. */
  372. $MR = $this->mr->fetchByID($MRID);
  373. $this->view->title = $this->lang->mr->viewDiff;
  374. $this->view->MR = $MR;
  375. $this->view->compile = $this->loadModel('compile')->getById($MR->compileID);
  376. if($MR->synced)
  377. {
  378. $rawMR = $this->mr->apiGetSingleMR($MR->repoID, $MR->mriid);
  379. $this->view->rawMR = $rawMR;
  380. if(!isset($rawMR->id) || empty($rawMR)) return $this->display();
  381. }
  382. $diffs = $this->mr->getDiffs($MR, $encoding);
  383. $arrange = $this->cookie->arrange ? $this->cookie->arrange : 'inline';
  384. if($this->server->request_method == 'POST')
  385. {
  386. if($this->post->arrange)
  387. {
  388. $arrange = $this->post->arrange;
  389. helper::setcookie('arrange', $arrange);
  390. }
  391. if($this->post->encoding) $encoding = $this->post->encoding;
  392. }
  393. $this->view->repo = $this->loadModel('repo')->getByID($MR->repoID);
  394. $this->view->diffs = $arrange == 'appose' ? $this->repo->getApposeDiff($diffs) : $diffs;
  395. $this->view->encoding = $encoding;
  396. $this->view->oldRevision = $MR->targetBranch;
  397. $this->view->newRevision = $MR->sourceBranch;
  398. $this->display();
  399. }
  400. /**
  401. * 审核合并请求。
  402. * Approval for this MR.
  403. *
  404. * @param int $MRID
  405. * @param string $action
  406. * @return void
  407. */
  408. public function approval($MRID, $action = 'approve')
  409. {
  410. $MR = $this->mr->fetchByID($MRID);
  411. if($_POST)
  412. {
  413. $comment = $this->post->comment;
  414. $result = $this->mr->approve($MR, $action, $comment);
  415. return $this->send($result);
  416. }
  417. $showCompileResult = false;
  418. if(!empty($MR->compileStatus))
  419. {
  420. $showCompileResult = true;
  421. $this->view->compileUrl = $this->createLink('job', 'view', "jobID={$MR->jobID}&compileID={$MR->compileID}");
  422. }
  423. $this->view->MR = $MR;
  424. $this->view->action = $action;
  425. $this->view->actions = $this->loadModel('action')->getList($this->app->rawModule, $MRID);
  426. $this->view->users = $this->loadModel('user')->getPairs('noletter|noclosed');
  427. $this->view->showCompileResult = $showCompileResult;
  428. $this->display();
  429. }
  430. /**
  431. * 关闭合并请求。
  432. * Close this MR.
  433. *
  434. * @param int $MRID
  435. * @access public
  436. * @return void
  437. */
  438. public function close($MRID)
  439. {
  440. $MR = $this->mr->fetchByID($MRID);
  441. $result = $this->mr->close($MR);
  442. return $this->send($result);
  443. }
  444. /**
  445. * 重新打开合并请求。
  446. * Reopen this MR.
  447. *
  448. * @param int $MRID
  449. * @access public
  450. * @return void
  451. */
  452. public function reopen($MRID)
  453. {
  454. $MR = $this->mr->fetchByID($MRID);
  455. return $this->send($this->mr->reopen($MR));
  456. }
  457. /**
  458. * 获取合并请求的关联信息。
  459. * link MR list.
  460. *
  461. * @param int $MRID
  462. * @param string $type
  463. * @param string $orderBy
  464. * @param string $param
  465. * @param int $recPerPage
  466. * @param int $pageID
  467. * @return void
  468. */
  469. public function link($MRID, $type = 'story', $orderBy = 'id_desc', $param = '', $recPerPage = 20, $pageID = 1)
  470. {
  471. $MR = $this->mr->fetchByID($MRID);
  472. $product = $this->mr->getMRProduct($MR);
  473. /* Load pager. */
  474. $this->app->loadClass('pager', true);
  475. $storyPager = new pager(0, $recPerPage, $type == 'story' ? $pageID : 1);
  476. $bugPager = new pager(0, $recPerPage, $type == 'bug' ? $pageID : 1);
  477. $taskPager = new pager(0, $recPerPage, $type == 'task' ? $pageID : 1);
  478. $productID = $product ? $product->id : 0;
  479. $stories = $this->mr->getLinkList($MRID, 'story', $type == 'story' ? $orderBy : '', $storyPager);
  480. $bugs = $this->mr->getLinkList($MRID, 'bug', $type == 'bug' ? $orderBy : '', $bugPager);
  481. $tasks = $this->mr->getLinkList($MRID, 'task', $type == 'task' ? $orderBy : '', $taskPager);
  482. $builds = $this->loadModel('build')->getBuildPairs($productID);
  483. $this->view->title = $this->lang->mr->common . $this->lang->hyphen . $this->lang->mr->link;
  484. $this->view->MR = $MR;
  485. $this->view->repoID = $MR->repoID;
  486. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  487. $this->view->stories = $stories;
  488. $this->view->bugs = $bugs;
  489. $this->view->tasks = $tasks;
  490. $this->view->product = $product;
  491. $this->view->storyPager = $storyPager;
  492. $this->view->bugPager = $bugPager;
  493. $this->view->taskPager = $taskPager;
  494. $this->view->type = $type;
  495. $this->view->builds = $builds;
  496. $this->view->orderBy = $orderBy;
  497. $this->view->param = $param;
  498. $this->display();
  499. }
  500. /**
  501. * 获取合并请求可关联的需求列表。
  502. * Link story to mr.
  503. *
  504. * @param int $MRID
  505. * @param int $repoID
  506. * @param string $browseType
  507. * @param int $param
  508. * @param string $orderBy
  509. * @param int $recTotal
  510. * @param int $recPerPage
  511. * @param int $pageID
  512. * @access public
  513. * @return void
  514. */
  515. public function linkStory($MRID, $repoID = 0, $browseType = '', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 10, $pageID = 1)
  516. {
  517. if(!empty($_POST['stories']))
  518. {
  519. $this->mr->link($MRID, 'story', $this->post->stories);
  520. if(dao::isError()) return $this->sendError(dao::getError());
  521. $link = $this->createLink($this->app->rawModule,'link', "MRID=$MRID&type=story&orderBy=$orderBy");
  522. return $this->sendSuccess(array('load' => $link, 'closeModal' => true));
  523. }
  524. $this->loadModel('story');
  525. /* Load pager. */
  526. $this->app->loadClass('pager', true);
  527. $pager = new pager($recTotal, $recPerPage, $pageID);
  528. /* Build search form. */
  529. $queryID = ($browseType == 'bySearch') ? (int) $param : 0;
  530. $this->mrZen->buildLinkStorySearchForm($MRID, $repoID, $orderBy, $queryID);
  531. $repo = $this->loadModel('repo')->fetchByID($repoID);
  532. $productID = $repo ? $repo->product : 0;
  533. $linkedStories = $this->mr->getLinkList($MRID, 'story');
  534. if($browseType == 'bySearch')
  535. {
  536. $this->session->set('repoID', $repoID);
  537. $allStories = $this->story->getBySearch('all', 0, $queryID, $orderBy, 0, 'story', array_keys($linkedStories), '', $pager);
  538. }
  539. else
  540. {
  541. $allStories = $this->story->getProductStories($productID, 'all', '0', 'draft,reviewing,active,changing', 'story', $orderBy, true, array_keys($linkedStories), $pager);
  542. }
  543. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  544. $this->view->allStories = $allStories;
  545. $this->view->MRID = $MRID;
  546. $this->view->browseType = $browseType;
  547. $this->view->param = $param;
  548. $this->view->orderBy = $orderBy;
  549. $this->view->repoID = $repoID;
  550. $this->view->pager = $pager;
  551. $this->display();
  552. }
  553. /**
  554. * 获取合并请求可关联的Bug列表。
  555. * Link bug to mr.
  556. *
  557. * @param int $MRID
  558. * @param int $repoID
  559. * @param string $browseType
  560. * @param int $param
  561. * @param string $orderBy
  562. * @param int $recTotal
  563. * @param int $recPerPage
  564. * @param int $pageID
  565. * @access public
  566. * @return void
  567. */
  568. public function linkBug($MRID, $repoID = 0, $browseType = '', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 10, $pageID = 1)
  569. {
  570. if(!empty($_POST['bugs']))
  571. {
  572. $this->mr->link($MRID, 'bug', $this->post->bugs);
  573. if(dao::isError()) return $this->sendError(dao::getError());
  574. $link = $this->createLink($this->app->rawModule,'link', "MRID=$MRID&type=bug&orderBy=$orderBy");
  575. return $this->sendSuccess(array('load' => $link, 'closeModal' => true));
  576. }
  577. $this->loadModel('bug');
  578. $queryID = ($browseType == 'bysearch') ? (int)$param : 0;
  579. $this->mrZen->buildLinkBugSearchForm($MRID, $repoID, $orderBy, $queryID);
  580. /* Load pager. */
  581. $this->app->loadClass('pager', true);
  582. $pager = new pager($recTotal, $recPerPage, $pageID);
  583. /* Build search form. */
  584. $repo = $this->loadModel('repo')->fetchByID($repoID);
  585. $productIdList = $repo ? explode(',', trim($repo->product, ',')) : 0;
  586. $linkedBugs = $this->mr->getLinkList($MRID, 'bug');
  587. if($browseType == 'bySearch')
  588. {
  589. $allBugs = $this->bug->getBySearch('bug', $productIdList, 0, 0, 0, $queryID, implode(',', array_keys($linkedBugs)), $orderBy, $pager);
  590. }
  591. else
  592. {
  593. $allBugs = $this->bug->getActiveBugs($productIdList, 0, '0', array_keys($linkedBugs), $pager, $orderBy);
  594. }
  595. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  596. $this->view->allBugs = $allBugs;
  597. $this->view->MRID = $MRID;
  598. $this->view->browseType = $browseType;
  599. $this->view->param = $param;
  600. $this->view->orderBy = $orderBy;
  601. $this->view->pager = $pager;
  602. $this->view->repoID = $repoID;
  603. $this->display();
  604. }
  605. /**
  606. * 获取合并请求可关联的任务列表。
  607. * Link task to mr.
  608. *
  609. * @param int $MRID
  610. * @param int $repoID
  611. * @param string $browseType
  612. * @param int $param
  613. * @param string $orderBy
  614. * @param int $recTotal
  615. * @param int $recPerPage
  616. * @param int $pageID
  617. * @access public
  618. * @return void
  619. */
  620. public function linkTask($MRID, $repoID = 0, $browseType = 'unclosed', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 10, $pageID = 1)
  621. {
  622. if(!empty($_POST['tasks']))
  623. {
  624. $this->mr->link($MRID, 'task', $this->post->tasks);
  625. if(dao::isError()) return $this->sendError(dao::getError());
  626. $link = $this->createLink($this->app->rawModule,'link', "MRID=$MRID&type=task&orderBy=$orderBy");
  627. return $this->sendSuccess(array('load' => $link, 'closeModal' => true));
  628. }
  629. /* Set browse type. */
  630. $browseType = strtolower($browseType);
  631. $queryID = ($browseType == 'bysearch') ? (int)$param : 0;
  632. /* Get executions by product. */
  633. $this->loadModel('product');
  634. $executions = array();
  635. $repo = $this->loadModel('repo')->fetchByID($repoID);
  636. $productIdList = $repo ? explode(',', trim($repo->product, ',')) : array();
  637. foreach($productIdList as $productID)
  638. {
  639. if(empty($productID)) continue;
  640. $executions = $executions + $this->product->getExecutionPairsByProduct((int)$productID);
  641. }
  642. $this->loadModel('execution');
  643. $this->mrZen->buildLinkTaskSearchForm($MRID, $repoID, $orderBy, $queryID, $executions);
  644. $linkedTasks = $this->mr->getLinkList($MRID, 'task');
  645. /* Get tasks by executions. */
  646. $allTasks = array();
  647. if($browseType == 'bysearch')
  648. {
  649. $allTasks = $this->execution->getTasks(0, 0, $executions, $browseType, $queryID, 0, $orderBy, null);
  650. }
  651. else
  652. {
  653. $this->loadModel('task');
  654. $queryStatus = $this->lang->task->statusList;
  655. unset($queryStatus['closed']);
  656. $condition = new stdclass();
  657. $condition->statusList = array_keys($queryStatus);
  658. $condition->executionList = array_keys($executions);
  659. $allTasks = $this->loadModel('task')->getListByCondition($condition);
  660. }
  661. /* Filter linked tasks. */
  662. $linkedTaskIDs = array_keys($linkedTasks);
  663. foreach($allTasks as $key => $task)
  664. {
  665. if(in_array($task->id, $linkedTaskIDs)) unset($allTasks[$key]);
  666. }
  667. $this->mrZen->processLinkTaskPager($recTotal, $recPerPage, $pageID, $allTasks);
  668. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  669. $this->view->MRID = $MRID;
  670. $this->view->browseType = $browseType;
  671. $this->view->param = $param;
  672. $this->view->orderBy = $orderBy;
  673. $this->view->repoID = $repoID;
  674. $this->display();
  675. }
  676. /**
  677. * 解除合并请求关联的对象。
  678. * UnLink an mr link.
  679. *
  680. * @param int $MRID
  681. * @param string $type
  682. * @param int $linkID
  683. * @access public
  684. * @return void
  685. */
  686. public function unlink($MRID, $type, $linkID)
  687. {
  688. $this->mr->unlink($MRID, $type, $linkID);
  689. if(dao::isError()) return $this->sendError(dao::getError());
  690. return $this->sendSuccess(array('message' => '', 'load' => $this->createLink($this->app->rawModule, 'link', "MRID=$MRID&type=$type")));
  691. }
  692. /**
  693. * 查看合并请求的提交记录。
  694. * Show the commit logs for this Merge Request.
  695. *
  696. * @param int $MRID
  697. * @return void
  698. */
  699. public function commitLogs($MRID)
  700. {
  701. $MR = $this->mr->fetchByID($MRID);
  702. $compile = $this->loadModel('compile')->getById($MR->compileID);
  703. $this->view->title = $this->lang->mr->commitLogs;
  704. $this->view->MR = $MR;
  705. $this->view->compile = $compile;
  706. if($MR->synced)
  707. {
  708. $rawMR = $this->mr->apiGetSingleMR($MR->repoID, $MR->mriid);
  709. $this->view->rawMR = $rawMR;
  710. if(!isset($rawMR->id) || empty($rawMR)) return $this->display();
  711. }
  712. $repo = $this->loadModel('repo')->getByID($MR->repoID);
  713. $commitLogs = $this->mr->apiGetMRCommits($MR->hostID, $MR->targetProject, $MR->mriid);
  714. foreach($commitLogs as $commitLog)
  715. {
  716. $commitLog->repoID = $MR->repoID;
  717. if(in_array(strtolower($repo->SCM), array('gitea', 'gogs')))
  718. {
  719. $commitLog->id = $commitLog->sha;
  720. $commitLog->committed_date = $commitLog->author->committer->date;
  721. $commitLog->committer_name = $commitLog->author->committer->name;
  722. $commitLog->committer_email = $commitLog->author->committer->email;
  723. $commitLog->title = $commitLog->commit->message;
  724. }
  725. $commitLog->id = substr($commitLog->id, 0, 10);
  726. }
  727. $this->view->commitLogs = $commitLogs;
  728. $this->view->repo = $repo;
  729. $this->display();
  730. }
  731. /**
  732. * 获取构建列表。
  733. * AJAX: Get job list.
  734. *
  735. * @param int $repoID
  736. * @return void
  737. */
  738. public function ajaxGetJobList($repoID)
  739. {
  740. $jobList = $this->loadModel('job')->getListByRepoID($repoID);
  741. if(!$jobList) return $this->send(array('message' => array()));
  742. $options = "<option value=''></option>";
  743. foreach($jobList as $job) $options .= "<option value='{$job->id}' data-name='{$job->name}'>[{$job->id}] {$job->name}</option>";
  744. $this->send($options);
  745. }
  746. /**
  747. * 检查是否有相同的合并请求。
  748. * Ajax check same opened mr for source branch.
  749. *
  750. * @param int $hostID
  751. * @access public
  752. * @return void
  753. */
  754. public function ajaxCheckSameOpened($hostID)
  755. {
  756. $sourceProject = $this->post->sourceProject;
  757. $sourceBranch = $this->post->sourceBranch;
  758. $targetProject = $this->post->targetProject;
  759. $targetBranch = $this->post->targetBranch;
  760. $result = $this->mr->checkSameOpened($hostID, (string)$sourceProject, $sourceBranch, (string)$targetProject, $targetBranch);
  761. echo json_encode($result);
  762. }
  763. /**
  764. * 获取分支权限。
  765. * Ajax get branch privileges.
  766. *
  767. * @param int $hostID
  768. * @param string $project
  769. * @access public
  770. * @return void
  771. */
  772. public function ajaxGetBranchPrivs($hostID, $project)
  773. {
  774. $host = $this->loadModel('pipeline')->getByID($hostID);
  775. if(in_array($host->type, array('gitea', 'gogs')))
  776. {
  777. $project = urldecode(base64_decode($project));
  778. }
  779. else
  780. {
  781. $project = (int)$project;
  782. }
  783. $branchPrivs = array();
  784. $branches = $this->loadModel($host->type)->apiGetBranchPrivs($hostID, $project);
  785. foreach($branches as $branch) $branchPrivs[$branch->name] = $branch->name;
  786. echo json_encode($branchPrivs);
  787. }
  788. /**
  789. * 从代码托管服务器同步合并请求到禅道。
  790. * AJAX: sync Merge Requests from API server.
  791. *
  792. * @param int $repoID
  793. * @return void
  794. */
  795. public function ajaxSyncMRs($repoID)
  796. {
  797. if(!$repoID) $this->sendSuccess();
  798. $repo = $this->loadModel('repo')->getByID($repoID);
  799. if($repo && in_array($repo->SCM, $this->config->repo->notSyncSCM)) $repo->serviceProject = (int)$repo->serviceProject;
  800. $rawMRs = $this->loadModel(strtolower($repo->SCM))->apiGetMergeRequests($repo->gitService, $repo->serviceProject);
  801. if(empty($rawMRs)) $this->sendSuccess();
  802. $MRs = $this->dao->select('`id`,`sourceProject`,`sourceBranch`,`targetProject`,`targetBranch`,`mriid`')->from(TABLE_MR)->where('repoID')->eq($repoID)->fetchAll();
  803. $existedMRs = array();
  804. $needSyncMRs = $rawMRs;
  805. foreach($rawMRs as $index => $rawMR)
  806. {
  807. if($rawMR->state == 'merged' || $rawMR->source_project_id != $rawMR->target_project_id)
  808. {
  809. unset($needSyncMRs[$index]);
  810. continue;
  811. }
  812. foreach($MRs as $MR)
  813. {
  814. if($MR->mriid == $rawMR->iid
  815. && $MR->sourceProject == $rawMR->source_project_id && $MR->sourceBranch == $rawMR->source_branch
  816. && $MR->targetProject == $rawMR->target_project_id && $MR->targetBranch == $rawMR->target_branch)
  817. {
  818. $existedMRs[$MR->id] = $needSyncMRs[$index];
  819. unset($needSyncMRs[$index]);
  820. }
  821. }
  822. }
  823. $this->mrZen->saveMrData($repo, $needSyncMRs);
  824. foreach($existedMRs as $mrID => $existedMR)
  825. {
  826. $MR = $this->mr->fetchByID($mrID);
  827. $status = $existedMR->state ?: '';
  828. $mergeStatus = $existedMR->merge_status ?: '';
  829. if($MR->title != $existedMR->title || $MR->status != $status || $MR->mergeStatus != $mergeStatus)
  830. {
  831. $newMR = new stdclass();
  832. $newMR->title = $existedMR->title;
  833. $newMR->status = $status;
  834. $newMR->isFlow = empty($existedMR->flow) ? 0 : 1;
  835. $newMR->mergeStatus = $mergeStatus;
  836. $this->dao->update(TABLE_MR)->data($newMR)->where('id')->eq($mrID)->exec();
  837. if(!dao::isError()) $this->loadModel('action')->create(empty($existedMR->flow) ? 'mr' : 'pullreq', $mrID, 'synced', '', '', 'system');
  838. }
  839. }
  840. $this->sendSuccess();
  841. }
  842. /**
  843. * 执行合并请求的构建。
  844. * AJAX exec MR job.
  845. *
  846. * @param int $MRID
  847. * @param int $jobID
  848. * @access public
  849. * @return void
  850. */
  851. public function ajaxExecJob($MRID, $jobID)
  852. {
  853. $this->mr->execJob($MRID, $jobID);
  854. return $this->sendSuccess(array('load' => true));
  855. }
  856. /**
  857. * 获取合并请求关联的对象。
  858. * AJAX get MR linked objects.
  859. *
  860. * @param int $MRID
  861. * @access public
  862. * @return void
  863. */
  864. public function ajaxLinkObjects($MRID)
  865. {
  866. $mr = $this->mr->fetchByID($MRID);
  867. if(!$mr) return false;
  868. return $this->mr->linkObjects($mr);
  869. }
  870. /**
  871. * 同步流水线的构建状态。
  872. * AJAX sync compile status.
  873. *
  874. * @param int $compileID
  875. * @access public
  876. * @return void
  877. */
  878. public function ajaxSyncCompile($compileID)
  879. {
  880. $this->loadModel('ci')->checkCompileStatus($compileID);
  881. return $this->sendSuccess(array('message' => $this->lang->mr->refreshSuccess, 'load' => true));
  882. }
  883. }