diff --git a/api/v1/entries/testresults.php b/api/v1/entries/testresults.php index 365e9f79c9..d6fa1c6b8f 100644 --- a/api/v1/entries/testresults.php +++ b/api/v1/entries/testresults.php @@ -60,18 +60,24 @@ class testresultsEntry extends entry $case = $this->loadModel('testcase')->getByID($caseID); $runID = $this->param('runID', 0); $version = $this->param('version', $case->version); + $steps = $this->getStepIDList($runID, $caseID, $version); $this->setPost('case', $caseID); + $this->setPost('version', $version); /* Set steps and expects. */ if(isset($this->requestBody->steps)) { $results = array(); $reals = array(); - foreach($this->requestBody->steps as $step) + foreach($this->requestBody->steps as $index => $step) { - $results[] = $step->result; - $reals[] = $step->real; + /* When post data more than case steps, break after take useful data. */ + if($index + 1 > count($steps)) break; + + $stepID = $steps[$index]; + $results[$stepID] = $step->result; + $reals[$stepID] = $step->real; } $this->setPost('steps', $results); $this->setPost('reals', $reals); @@ -85,4 +91,35 @@ class testresultsEntry extends entry return $this->send(200, array()); } + + /** + * Get steps key. + * + * @param int $runID + * @access public + * @return array + */ + public function getStepIDList($runID, $caseID, $version) + { + if($runID) + { + $run = $this->testtask->getRunById($runID); + } + else + { + $run = new stdclass(); + $run->case = $this->loadModel('testcase')->getById($caseID, $version); + } + + foreach($run->case->steps as $key => $step) + { + /* Unset step if step is a group. */ + if($step->type == 'group') + { + unset($run->case->steps[$key]); + } + } + + return array_keys($run->case->steps); + } } diff --git a/config/config.php b/config/config.php index 6429afecfa..aa6b30cbfa 100644 --- a/config/config.php +++ b/config/config.php @@ -24,7 +24,7 @@ $config->timezone = 'Asia/Shanghai'; // 时区设置。 The tim $config->webRoot = ''; // URL根目录。 The root path of the url. $config->customSession = false; // 是否开启自定义session的存储路径。Whether custom the session save path. $config->edition = 'open'; // 设置系统的edition,可选值:open|biz|max。Set edition, optional: open|biz|max. -$config->tabSession = true; // 是否开启浏览器新标签独立session. +$config->tabSession = false; // 是否开启浏览器新标签独立session. /* 框架路由相关设置。Routing settings. */ $config->requestType = 'PATH_INFO'; // 请求类型:PATH_INFO|PATHINFO2|GET。 The request type: PATH_INFO|PATH_INFO2|GET. diff --git a/config/zentaopms.php b/config/zentaopms.php index e542fd9dd3..3a8b69d703 100644 --- a/config/zentaopms.php +++ b/config/zentaopms.php @@ -422,7 +422,7 @@ $config->maxPriValue = '256'; $config->importWhiteList = array('user', 'task', 'story', 'bug', 'testcase', 'feedback', 'ticket'); $config->featureGroup = new stdclass(); -$config->featureGroup->product = array('roadmap', 'track', 'URStory'); +$config->featureGroup->product = array('roadmap', 'track', 'UR'); $config->featureGroup->scrum = array(); $config->featureGroup->waterfall = array('track'); $config->featureGroup->assetlib = array(); diff --git a/db/update18.0.sql b/db/update17.8.sql similarity index 99% rename from db/update18.0.sql rename to db/update17.8.sql index 2246ae9857..3df1275dbd 100644 --- a/db/update18.0.sql +++ b/db/update17.8.sql @@ -2,6 +2,9 @@ ALTER TABLE `zt_product` ADD `shadow` tinyint(1) unsigned NOT NULL AFTER `code`; ALTER TABLE `zt_project` ADD `hasProduct` tinyint(1) unsigned NOT NULL DEFAULT 1 AFTER `code`; ALTER table `zt_project` ADD `multiple` enum('0', '1') NOT NULL DEFAULT '1'; ALTER TABLE `zt_repo` ADD `projects` varchar(255) NOT NULL AFTER `product`; +ALTER TABLE `zt_release` CHANGE `project` `project` varchar(255) NOT NULL; +ALTER TABLE `zt_release` CHANGE `branch` `branch` varchar(255) NOT NULL; +ALTER TABLE `zt_release` CHANGE `build` `build` varchar(255) NOT NULL; CREATE OR REPLACE VIEW `ztv_normalproduct` AS SELECT * FROM `zt_product` WHERE `shadow` = 0; @@ -32,3 +35,7 @@ REPLACE INTO `zt_report` (`code`, `name`, `module`, `sql`, `vars`, `langs`, `par ('bug-resolve', '{\"zh-cn\":\"Bug\\u89e3\\u51b3\\u8868\",\"zh-tw\":\"Bug\\u89e3\\u6c7a\\u8868\",\"en\":\"Solved Bugs\"}', ',test', 'select *,if($product=\'\',0,product) as customproduct from TABLE_BUG where deleted=\'0\' and resolution!=\'\' and if($startDate=\'\',1,resolvedDate>=$startDate) and if($endDate=\'\',1,resolvedDate<=$endDate) having customproduct=$product', '{\"varName\":[\"product\",\"startDate\",\"endDate\"],\"showName\":[\"\\u4ea7\\u54c1\",\"\\u89e3\\u51b3\\u65e5\\u671f\\u5f00\\u59cb\",\"\\u89e3\\u51b3\\u65e5\\u671f\\u7ed3\\u675f\"],\"requestType\":[\"select\",\"date\",\"date\"],\"selectList\":[\"product\",\"user\",\"user\"],\"default\":[\"\",\"$MONTHBEGIN\",\"$MONTHEND\"]}', '{\"count\":{\"zh-cn\":\"\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u9700\\u6c42\\u6570\",\"en\":\"Stories\"},\"done\":{\"zh-cn\":\"\\u5b8c\\u6210\\u6570\",\"zh-tw\":\"\\u5b8c\\u6210\\u6570\",\"en\":\"Done\"}}', '{\"group1\":\"resolvedBy\",\"isUser\":{\"group1\":[\"1\"]},\"group2\":\"\",\"reportField\":[\"resolution\"],\"reportType\":[\"count\"],\"sumAppend\":[\"\"],\"reportTotal\":[\"1\"],\"percent\":[\"1\"],\"contrast\":[\"crystalTotal\"],\"showAlone\":[\"1\"]}', 2, '{\"zh-cn\":\"\\u5217\\u51fa\\u89e3\\u51b3\\u7684Bug\\u603b\\u6570\\uff0c\\u89e3\\u51b3\\u65b9\\u6848\\u7684\\u5206\\u5e03\\uff0c\\u5360\\u7684\\u6bd4\\u4f8b\\uff08\\u8be5\\u7528\\u6237\\u89e3\\u51b3\\u7684Bug\\u7684\\u6570\\u91cf\\u5360\\u6240\\u6709\\u7684\\u89e3\\u51b3\\u7684Bug\\u7684\\u6570\\u91cf)\\u3002\",\"zh-tw\":\"\\u5217\\u51fa\\u89e3\\u6c7a\\u7684Bug\\u7e3d\\u6578\\uff0c\\u89e3\\u6c7a\\u65b9\\u6848\\u7684\\u5206\\u5e03\\uff0c\\u5360\\u7684\\u6bd4\\u4f8b\\uff08\\u8a72\\u7528\\u6236\\u89e3\\u6c7a\\u7684Bug\\u7684\\u6578\\u91cf\\u5360\\u6240\\u6709\\u7684\\u89e3\\u6c7a\\u7684Bug\\u7684\\u6578\\u91cf)\\u3002\",\"en\":\"percentage:self resolved \\/ all resolved\"}', 'admin', '2015-07-24 13:44:25'), ('project-progress', '{\"zh-cn\":\"\\u9879\\u76ee\\u8fdb\\u5c55\\u8868\",\"zh-tw\":\"\\u9805\\u76ee\\u9032\\u5c55\\u8868\",\"en\":\"Project Progress Report\",\"de\":\"Project Progress Report\",\"fr\":\"Project Progress Report\",\"vi\":\"Project Progress Report\",\"ja\":\"Project Progress Report\"}', ',project', 'select t1.id,t4.name as project,t4.id,IF(t4.multiple="1",t1.name,"") as execution,t1.status,t2.number as tasks,round(t2.consumed,2) as consumed,round(t2.`left`,2) as `left`,t3.stories,t2.undone as undoneTask,t3.undone as undoneStory,t2.totalReal from TABLE_EXECUTION as t1 \r\nleft join ztv_executionsummary as t2 on t1.id=t2.execution\r\nleft join ztv_projectstories as t3 on t1.id=t3.execution\r\nleft join TABLE_PROJECT as t4 on t4.id=t1.project\r\nwhere t1.deleted=\'0\' and t1.type in (\'sprint\',\'stage\') and if($project=\'\',1,t4.id=$project) and if($execution=\'\',1,t1.id=$execution) and if($status=\'\',1,t1.status=$status)', '{\"varName\":[\"project\",\"execution\",\"status\"],\"showName\":[\"\\u9879\\u76ee\\u5217\\u8868\",\"\\u6267\\u884c\\u5217\\u8868\",\"\\u6267\\u884c\\u72b6\\u6001\"],\"requestType\":[\"select\",\"select\",\"select\"],\"selectList\":[\"project\",\"execution\",\"project.status\"],\"default\":[\"\",\"\",\"\"]}', '{\"stories\":{\"zh-cn\":\"\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u9700\\u6c42\\u6570\",\"en\":\"Stories\"},\"tasks\":{\"zh-cn\":\"\\u4efb\\u52a1\\u6570\",\"zh-tw\":\"\\u4efb\\u52a1\\u6570\",\"en\":\"Tasks\"},\"undoneStory\":{\"zh-cn\":\"\\u5269\\u4f59\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u5269\\u4f59\\u9700\\u6c42\\u6570\",\"en\":\"Undone Story\"},\"undoneTask\":{\"zh-cn\":\"\\u5269\\u4f59\\u4efb\\u52a1\\u6570\",\"zh-tw\":\"\\u5269\\u4f59\\u4efb\\u52a1\\u6570\",\"en\":\"Undone Task\"},\"consumed\":{\"zh-cn\":\"\\u5df2\\u6d88\\u8017\\u5de5\\u65f6\",\"zh-tw\":\"\\u5df2\\u6d88\\u8017\\u5de5\\u65f6\",\"en\":\"Cost(h)\"},\"left\":{\"zh-cn\":\"\\u5269\\u4f59\\u5de5\\u65f6\",\"zh-tw\":\"\\u5269\\u4f59\\u5de5\\u65f6\",\"en\":\"Left(h)\"},\"consumedPercent\":{\"zh-cn\":\"\\u8fdb\\u5ea6\",\"zh-tw\":\"\\u8fdb\\u5ea6\",\"en\":\"Process\"},\"execution\":{\"zh-cn\":\"\\u6267\\u884c\\u540d\\u79f0\"}}', '{\"group1\":\"project\",\"group2\":\"execution\",\"reportField\":[\"stories\",\"undoneStory\",\"tasks\",\"undoneTask\",\"left\",\"consumed\"],\"reportType\":[\"sum\",\"sum\",\"sum\",\"sum\",\"sum\",\"sum\"],\"sumAppend\":[\"stories\",\"undoneStory\",\"tasks\",\"undoneTask\",\"left\",\"consumed\"],\"percent\":{\"5\":\"1\"},\"contrast\":{\"5\":\"totalReal\"},\"showAlone\":{\"5\":\"1\"}}', 2, '{\"zh-cn\":\"\\u9879\\u76ee\\u7684\\u9700\\u6c42\\u6570\\uff0c\\u4efb\\u52a1\\u6570\\uff0c\\u5df2\\u6d88\\u8017\\u5de5\\u65f6\\uff0c\\u5269\\u4f59\\u5de5\\u65f6\\uff0c\\u5269\\u4f59\\u9700\\u6c42\\u6570\\uff0c\\u5269\\u4f59\\u4efb\\u52a1\\u6570\\uff0c\\u8fdb\\u5ea6\\u3002\",\"zh-tw\":\"\\u9805\\u76ee\\u7684\\u9700\\u6c42\\u6578\\uff0c\\u4efb\\u52d9\\u6578\\uff0c\\u5df2\\u6d88\\u8017\\u5de5\\u6642\\uff0c\\u5269\\u9918\\u5de5\\u6642\\uff0c\\u5269\\u9918\\u9700\\u6c42\\u6578\\uff0c\\u5269\\u9918\\u4efb\\u52d9\\u6578\\uff0c\\u9032\\u5ea6\\u3002\",\"en\":\"\",\"de\":\"\",\"fr\":\"\",\"vi\":\"\",\"ja\":\"\"}', 'admin', '2015-07-23 14:03:06'), ('projectbug-type', '{\"zh-cn\":\"\\u9879\\u76eeBug\\u7c7b\\u578b\\u7edf\\u8ba1\\u8868\",\"zh-tw\":\"\\u9805\\u76eeBug\\u985e\\u578b\\u7d71\\u8a08\\u8868\",\"en\":\"Project Bug Type\",\"de\":\"Project Bug Type\",\"fr\":\"Project Bug Type\",\"vi\":\"Project Bug Type\",\"ja\":\"Project Bug Type\"}', ',project,test', 'select t1.id,t3.name as project,t3.id,IF(t3.multiple="1",t1.name,"") as execution,t2.id as bugID,t2.type from TABLE_EXECUTION as t1 \r\nleft join TABLE_BUG as t2 on t1.id=t2.execution\r\nleft join TABLE_PROJECT as t3 on t3.id=t1.project\r\nwhere t1.deleted=\'0\' and t2.deleted=\'0\' and if($project=\'\',1,t3.id=$project) and if($execution=\'\',1,t1.id=$execution)', '{\"varName\":[\"project\",\"execution\"],\"showName\":[\"\\u9879\\u76ee\\u5217\\u8868\",\"\\u6267\\u884c\\u5217\\u8868\"],\"requestType\":[\"select\",\"select\"],\"selectList\":[\"project\",\"execution\"],\"default\":[\"\",\"\"]}', '{\"stories\":{\"zh-cn\":\"\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u9700\\u6c42\\u6570\",\"en\":\"Stories\"},\"tasks\":{\"zh-cn\":\"\\u4efb\\u52a1\\u6570\",\"zh-tw\":\"\\u4efb\\u52a1\\u6570\",\"en\":\"Tasks\"},\"undoneStory\":{\"zh-cn\":\"\\u5269\\u4f59\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u5269\\u4f59\\u9700\\u6c42\\u6570\",\"en\":\"Undone Story\"},\"undoneTask\":{\"zh-cn\":\"\\u5269\\u4f59\\u4efb\\u52a1\\u6570\",\"zh-tw\":\"\\u5269\\u4f59\\u4efb\\u52a1\\u6570\",\"en\":\"Undone Task\"},\"consumed\":{\"zh-cn\":\"\\u5df2\\u6d88\\u8017\\u5de5\\u65f6\",\"zh-tw\":\"\\u5df2\\u6d88\\u8017\\u5de5\\u65f6\",\"en\":\"Cost(h)\"},\"left\":{\"zh-cn\":\"\\u5269\\u4f59\\u5de5\\u65f6\",\"zh-tw\":\"\\u5269\\u4f59\\u5de5\\u65f6\",\"en\":\"Left(h)\"},\"consumedPercent\":{\"zh-cn\":\"\\u8fdb\\u5ea6\",\"zh-tw\":\"\\u8fdb\\u5ea6\",\"en\":\"Process\"},\"execution\":{\"zh-cn\":\"\\u6267\\u884c\\u540d\\u79f0\"}}', '{\"group1\":\"project\",\"group2\":\"execution\",\"reportField\":[\"type\"],\"reportType\":[\"count\"],\"sumAppend\":[\"\"]}', 2, '{\"zh-cn\":\"\\u6309\\u7167\\u9879\\u76ee\\u7edf\\u8ba1Bug\\u7684\\u7c7b\\u578b\\u5206\\u5e03\\u60c5\\u51b5\\u3002\",\"zh-tw\":\"\\u6309\\u7167\\u9805\\u76ee\\u7d71\\u8a08Bug\\u7684\\u985e\\u578b\\u5206\\u5e03\\u60c5\\u6cc1\\u3002\",\"en\":\"\",\"de\":\"\",\"fr\":\"\",\"vi\":\"\",\"ja\":\"\"}', 'admin', '2015-08-04 13:54:22'); + +ALTER TABLE `zt_project` ADD `division` enum('0','1') COLLATE 'utf8_general_ci' NOT NULL DEFAULT '1'; + +ALTER TABLE `zt_build` ADD `builds` varchar(255) NOT NULL AFTER `execution`; diff --git a/db/zentao.sql b/db/zentao.sql index 0a289ef9fe..037ee010f2 100755 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -323,6 +323,7 @@ CREATE TABLE IF NOT EXISTS `zt_build` ( `product` mediumint(8) unsigned NOT NULL default '0', `branch` mediumint(8) unsigned NOT NULL default '0', `execution` mediumint(8) unsigned NOT NULL default '0', + `builds` varchar(255) NOT NULL, `name` char(150) NOT NULL, `scmPath` char(255) NOT NULL, `filePath` char(255) NOT NULL, @@ -1280,10 +1281,10 @@ CREATE TABLE IF NOT EXISTS `zt_relation` ( -- DROP TABLE IF EXISTS `zt_release`; CREATE TABLE IF NOT EXISTS `zt_release` ( `id` mediumint(8) unsigned NOT NULL auto_increment, - `project` mediumint(8) unsigned NOT NULL, + `project` varchar(255) NOT NULL, `product` mediumint(8) unsigned NOT NULL default '0', - `branch` mediumint(8) unsigned NOT NULL default '0', - `build` mediumint(8) unsigned NOT NULL, + `branch` varchar(255) NOT NULL default '0', + `build` varchar(255) NOT NULL, `name` varchar(255) NOT NULL default '', `marker` enum('0','1') NOT NULL default '0', `date` date NOT NULL, diff --git a/framework/api/helper.class.php b/framework/api/helper.class.php index a6db31970e..f24358735e 100644 --- a/framework/api/helper.class.php +++ b/framework/api/helper.class.php @@ -50,6 +50,50 @@ class helper extends baseHelper return isset($viewType) ? $viewType : $config->default->view; } + /** + * Verify that the system has opened on the feature. + * + * @param string $feature scrum_risk | risk | scrum + * @static + * @access public + * @return bool + */ + public static function hasFeature($feature) + { + global $config; + + if(strpos($feature, '_') !== false) + { + $code = explode('_', $feature); + $code = $code[0] . ucfirst($code[1]); + return strpos(",$config->disabledFeatures,", ",{$code},") === false; + } + else + { + if($feature == 'product' or $feature == 'scrum' or $feature == 'waterfall') return strpos(",$config->disabledFeatures,", ",{$feature},") === false; + + $hasFeature = false; + foreach($config->featureGroup as $group => $modules) + { + if($feature == $group) + { + foreach($modules as $module) + { + if(helper::hasFeature("{$group}_{$module}")) $hasFeature = true; + } + } + else + { + foreach($modules as $module) + { + if($feature == $module and helper::hasFeature("{$group}_{$module}")) $hasFeature = true; + } + } + } + return $hasFeature && strpos(",$config->disabledFeatures,", ",{$feature},") === false; + } + } + /** * Encode json for $.parseJSON * diff --git a/framework/base/helper.class.php b/framework/base/helper.class.php index 0e5ab9fcf8..1089b8d145 100644 --- a/framework/base/helper.class.php +++ b/framework/base/helper.class.php @@ -740,6 +740,19 @@ class baseHelper session_write_close(); session_id($sessionID); + if(ini_get('session.save_handler') == 'user' and isset($_GET['tid'])) + { + $ztSessionHandler = new ztSessionHandler($_GET['tid']); + session_set_save_handler( + array($ztSessionHandler, "open"), + array($ztSessionHandler, "close"), + array($ztSessionHandler, "read"), + array($ztSessionHandler, "write"), + array($ztSessionHandler, "destroy"), + array($ztSessionHandler, "gc") + ); + register_shutdown_function('session_write_close'); + } session_start(); } diff --git a/framework/base/router.class.php b/framework/base/router.class.php index b50611fe17..5caa5e3582 100644 --- a/framework/base/router.class.php +++ b/framework/base/router.class.php @@ -1004,7 +1004,6 @@ class baseRouter array($ztSessionHandler, "destroy"), array($ztSessionHandler, "gc") ); - register_shutdown_function('session_write_close'); } } @@ -3169,6 +3168,7 @@ class ztSessionHandler { $this->tagID = $tagID; ini_set('session.save_handler', 'files'); + register_shutdown_function('session_write_close'); } /** @@ -3263,15 +3263,16 @@ class ztSessionHandler public function write($id, $sessData) { $sessFile = $this->getSessionFile($id); + touch($sessFile); + touch($this->rawFile); if(md5_file($sessFile) == md5($sessData)) return true; - if(file_put_contents($sessFile, $sessData)) + if(file_put_contents($sessFile, $sessData, LOCK_EX)) { - if(!file_exists($this->rawFile)) touch($this->rawFile); if(strpos($sessData, 'user|') !== false) { - $rawSessContent = (string) file_get_contents($this->rawFile); - if(strpos($rawSessContent, 'user|') === false) file_put_contents($this->rawFile, $sessData); + $rawSessContent = (string) file_get_contents($this->rawFile, false, null, 0, 1024 * 2); + if(strpos($rawSessContent, 'user|') === false) file_put_contents($this->rawFile, $sessData, LOCK_EX); } return true; @@ -3290,9 +3291,10 @@ class ztSessionHandler public function destroy($id) { $sessFile = $this->getSessionFile($id); - unlink($sessFile); - unlink($this->rawFile); + if(file_exists($sessFile)) unlink($sessFile); + if(file_exists($this->rawFile)) unlink($this->rawFile); touch($sessFile); + touch($this->rawFile); return true; } diff --git a/framework/helper.class.php b/framework/helper.class.php index c83d2c3e07..2b352119c7 100644 --- a/framework/helper.class.php +++ b/framework/helper.class.php @@ -98,25 +98,20 @@ class helper extends baseHelper { if($feature == 'product' or $feature == 'scrum' or $feature == 'waterfall') return strpos(",$config->disabledFeatures,", ",{$feature},") === false; - $hasFeature = false; + $hasFeature = false; + $canConfigFeature = false; foreach($config->featureGroup as $group => $modules) { - if($feature == $group) + foreach($modules as $module) { - foreach($modules as $module) + if($feature == $group or $feature == $module) { + $canConfigFeature = true; if(helper::hasFeature("{$group}_{$module}")) $hasFeature = true; } } - else - { - foreach($modules as $module) - { - if($feature == $module and helper::hasFeature("{$group}_{$module}")) $hasFeature = true; - } - } } - return $hasFeature && strpos(",$config->disabledFeatures,", ",{$feature},") === false; + return !$canConfigFeature or ($hasFeature && strpos(",$config->disabledFeatures,", ",{$feature},") === false); } } diff --git a/module/action/model.php b/module/action/model.php index a91ae9f22c..cd6a848c36 100755 --- a/module/action/model.php +++ b/module/action/model.php @@ -386,7 +386,7 @@ class actionModel extends model $name = $execution->name; $method = $execution->type == 'kanban' ? 'kanban' : 'view'; - $action->extra = (!common::hasPriv('execution', $method) or ($method == 'kanban' and isonlybody())) ? $name : html::a(helper::createLink('execution', $method, "executionID=$action->execution"), $name); + $action->extra = (!common::hasPriv('execution', $method) or ($method == 'kanban' and isonlybody())) ? $name : html::a(helper::createLink('execution', $method, "executionID=$action->execution"), $name, '', "data-app='execution'"); } } elseif($actionName == 'linked2project') diff --git a/module/admin/lang/de.php b/module/admin/lang/de.php index 4760cafaeb..fd3014cc10 100644 --- a/module/admin/lang/de.php +++ b/module/admin/lang/de.php @@ -88,7 +88,7 @@ $lang->admin->setModule->auditplan = 'QA'; $lang->admin->setModule->meeting = 'Meeting'; $lang->admin->setModule->roadmap = 'Roadmap'; $lang->admin->setModule->track = 'Track'; -$lang->admin->setModule->URStory = 'URStroy'; +$lang->admin->setModule->UR = 'URStroy'; $lang->admin->setModule->researchplan = 'Researchplan'; $lang->admin->setModule->gapanalysis = 'Gapanalysis'; $lang->admin->setModule->storylib = 'Story Lib'; diff --git a/module/admin/lang/en.php b/module/admin/lang/en.php index 6eb18cc6c3..b7fcb08a16 100644 --- a/module/admin/lang/en.php +++ b/module/admin/lang/en.php @@ -88,7 +88,7 @@ $lang->admin->setModule->auditplan = 'QA'; $lang->admin->setModule->meeting = 'Meeting'; $lang->admin->setModule->roadmap = 'Roadmap'; $lang->admin->setModule->track = 'Track'; -$lang->admin->setModule->URStory = 'URStroy'; +$lang->admin->setModule->UR = 'URStroy'; $lang->admin->setModule->researchplan = 'Researchplan'; $lang->admin->setModule->gapanalysis = 'Gapanalysis'; $lang->admin->setModule->storylib = 'Story Lib'; diff --git a/module/admin/lang/fr.php b/module/admin/lang/fr.php index 8df60acc16..efe3d511e6 100644 --- a/module/admin/lang/fr.php +++ b/module/admin/lang/fr.php @@ -88,7 +88,7 @@ $lang->admin->setModule->auditplan = 'QA'; $lang->admin->setModule->meeting = 'Meeting'; $lang->admin->setModule->roadmap = 'Roadmap'; $lang->admin->setModule->track = 'Track'; -$lang->admin->setModule->URStory = 'URStroy'; +$lang->admin->setModule->UR = 'URStroy'; $lang->admin->setModule->researchplan = 'Researchplan'; $lang->admin->setModule->gapanalysis = 'Gapanalysis'; $lang->admin->setModule->storylib = 'Story Lib'; diff --git a/module/admin/lang/zh-cn.php b/module/admin/lang/zh-cn.php index 95a21c0810..c573762af5 100644 --- a/module/admin/lang/zh-cn.php +++ b/module/admin/lang/zh-cn.php @@ -88,7 +88,7 @@ $lang->admin->setModule->auditplan = 'QA'; $lang->admin->setModule->meeting = '会议'; $lang->admin->setModule->roadmap = '路线图'; $lang->admin->setModule->track = '矩阵'; -$lang->admin->setModule->URStory = '用户需求'; +$lang->admin->setModule->UR = '用户需求'; $lang->admin->setModule->researchplan = '调研'; $lang->admin->setModule->gapanalysis = '培训'; $lang->admin->setModule->storylib = '需求库'; diff --git a/module/block/view/contribute.html.php b/module/block/view/contribute.html.php index 618465eb87..58afbe9e9d 100644 --- a/module/block/view/contribute.html.php +++ b/module/block/view/contribute.html.php @@ -33,6 +33,7 @@
createLink('my', 'contribute', 'mode=testcase&type=openedbyme'), (int)$data['createdCases']);?>
config->edition == 'max'):?> +
block->createdRisks;?>
createLink('my', 'contribute', 'mode=risk'), (int)$data['createdRisks']);?>
@@ -41,6 +42,8 @@
block->resolvedRisks;?>
+ +
block->createdIssues;?>
createLink('my', 'contribute', 'mode=issue'), (int)$data['createdIssues']);?>
@@ -50,6 +53,7 @@
+
block->createdDocs;?>
createLink('my', 'contribute', 'mode=doc&type=openedbyme'), (int)$data['createdDocs']);?>
diff --git a/module/block/view/flowchart.html.php b/module/block/view/flowchart.html.php index 031dfbb2d4..157cd63397 100644 --- a/module/block/view/flowchart.html.php +++ b/module/block/view/flowchart.html.php @@ -46,5 +46,5 @@ .block-sm .row-3 .flow-item-1, .block-sm .row-3 .flow-item-3 {width: 25%} .block-sm .flow-item > div:after {right: -6px;} -.flow-item-display {overflow: hidden; white-space:nowrap; text-overflow: ellipsis;} +.flow-item-display {overflow: hidden; white-space:nowrap; text-overflow: clip;} diff --git a/module/bug/control.php b/module/bug/control.php index 31c6beb2fe..5508217ca8 100755 --- a/module/bug/control.php +++ b/module/bug/control.php @@ -2332,8 +2332,8 @@ class bug extends control { if($_POST) { - $this->loadModel('port'); - $this->session->set('bugPortParams', array('productID' => $productID, 'executionID' => $executionID, 'branch' => 'all')); + $this->loadModel('transfer'); + $this->session->set('bugTransferParams', array('productID' => $productID, 'executionID' => $executionID, 'branch' => 'all')); if(!$productID) { $this->config->bug->datatable->fieldList['module']['dataSource']['method'] = 'getAllModulePairs'; @@ -2343,7 +2343,7 @@ class bug extends control $this->config->bug->datatable->fieldList['execution']['dataSource'] = array('module' => 'execution', 'method' => 'getPairs', 'params' => $executionID); } - $this->port->export('bug'); + $this->transfer->export('bug'); $this->fetch('file', 'export2' . $_POST['fileType'], $_POST); } $product = $this->loadModel('product')->getByID($productID); diff --git a/module/bug/model.php b/module/bug/model.php index 578777eb5d..5d17a0c4af 100644 --- a/module/bug/model.php +++ b/module/bug/model.php @@ -1969,25 +1969,40 @@ class bugModel extends model */ public function getProductLeftBugs($build, $productID, $branch = '', $linkedBugs = '', $pager = null) { - $build = $this->dao->select('*')->from(TABLE_BUILD)->where('id')->eq($build)->fetch(); - if(empty($build->execution)) return array(); + $builds = $this->dao->select('*')->from(TABLE_BUILD)->where('id')->in($build)->fetchAll('id'); + + $executionIdList = array(); + foreach($builds as $build) + { + if(empty($build->execution)) continue; + $executionIdList[$build->execution] = $build->execution; + } + if(empty($executionIdList)) return array(); + + $executions = $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->in($executionIdList)->fetch(); + $minBegin = ''; + $maxEnd = ''; + foreach($executions as $execution) + { + if(empty($minBegin) or $minBegin > $execution->begin) $minBegin = $execution->begin; + if(empty($maxEnd) or $maxEnd < $execution->end) $maxEnd = $execution->end; + } - $execution = $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->eq($build->execution)->fetch(); $beforeBuilds = $this->dao->select('t1.id')->from(TABLE_BUILD)->alias('t1') ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution=t2.id') ->where('t1.product')->eq($productID) ->andWhere('t2.status')->ne('done') ->andWhere('t2.deleted')->eq(0) ->andWhere('t1.deleted')->eq(0) - ->andWhere('t1.date')->lt($execution->begin) + ->andWhere('t1.date')->lt($minBegin) ->fetchPairs('id', 'id'); $bugs = $this->dao->select('*')->from(TABLE_BUG)->where('deleted')->eq(0) ->andWhere('product')->eq($productID) ->andWhere('toStory')->eq(0) - ->andWhere('openedDate')->ge($execution->begin) - ->andWhere('openedDate')->le($execution->end) - ->andWhere("(status = 'active' OR resolvedDate > '{$execution->end}')") + ->andWhere('openedDate')->ge($minBegin) + ->andWhere('openedDate')->le($maxEnd) + ->andWhere("(status = 'active' OR resolvedDate > '{$maxEnd}')") ->andWhere('openedBuild')->notin($beforeBuilds) ->beginIF($linkedBugs)->andWhere('id')->notIN($linkedBugs)->fi() ->beginIF($branch !== '')->andWhere('branch')->in("0,$branch")->fi() @@ -2067,17 +2082,26 @@ class bugModel extends model */ public function getReleaseBugs($buildID, $productID, $branch = 0, $linkedBugs = '', $pager = null) { - $execution = $this->dao->select('t1.id,t1.begin')->from(TABLE_EXECUTION)->alias('t1') + $executions = $this->dao->select('t1.id,t1.begin')->from(TABLE_EXECUTION)->alias('t1') ->leftJoin(TABLE_BUILD)->alias('t2')->on('t1.id = t2.execution') - ->where('t2.id')->eq($buildID) - ->fetch(); + ->where('t2.id')->in($buildID) + ->fetchAll('id'); + + $condition = 'execution NOT ' . helper::dbIN(array_keys($executions)); + $minBegin = ''; + foreach($executions as $execution) + { + if(empty($minBegin) or $minBegin > $execution->begin) $minBegin = $execution->begin; + $condition .= " OR (execution = '{$execution->id}' AND openedDate < '{$execution->begin}')"; + } + $bugs = $this->dao->select('*')->from(TABLE_BUG) - ->where('resolvedDate')->ge($execution->begin) + ->where('resolvedDate')->ge($minBegin) ->andWhere('resolution')->ne('postponed') ->andWhere('product')->eq($productID) ->beginIF($linkedBugs)->andWhere('id')->notIN($linkedBugs)->fi() ->beginIF($branch)->andWhere('branch')->in("0,$branch")->fi() - ->andWhere("(execution != '$execution->id' OR (execution = '$execution->id' and openedDate < '$execution->begin'))") + ->andWhere("$condition)") ->andWhere('deleted')->eq(0) ->orderBy('openedDate ASC') ->page($pager) @@ -3648,7 +3672,7 @@ class bugModel extends model public function getRelatedObjects($object, $pairs = '') { /* Get bugs. */ - $bugs = $this->loadModel('port')->getQueryDatas('bug'); + $bugs = $this->loadModel('transfer')->getQueryDatas('bug'); /* Get related objects id lists. */ $relatedObjectIdList = array(); diff --git a/module/build/config.php b/module/build/config.php index c2522ab8cb..20b5917a4c 100644 --- a/module/build/config.php +++ b/module/build/config.php @@ -2,8 +2,8 @@ $config->build = new stdclass(); $config->build->create = new stdclass(); $config->build->edit = new stdclass(); -$config->build->create->requiredFields = 'product,execution,name,builder,date'; -$config->build->edit->requiredFields = 'product,execution,name,builder,date'; +$config->build->create->requiredFields = 'product,name,builder,date'; +$config->build->edit->requiredFields = 'product,name,builder,date'; $config->build->editor = new stdclass(); $config->build->editor->create = array('id' => 'desc', 'tools' => 'simpleTools'); diff --git a/module/build/control.php b/module/build/control.php index 74ea084661..537ba935d0 100644 --- a/module/build/control.php +++ b/module/build/control.php @@ -49,13 +49,12 @@ class build extends control if(!empty($_POST)) { - $executionID = empty($executionID) ? $this->post->execution : $executionID; - if(empty($executionID)) dao::$errors['execution'] = $this->lang->build->emptyExecution; + if(empty($executionID) && $this->app->tab == 'execution') dao::$errors['execution'] = $this->lang->build->emptyExecution; if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); if(defined('TUTORIAL')) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true)); // Fix bug #21095. - $buildID = $this->build->create($executionID); + $buildID = $this->build->create($executionID, $projectID); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); $this->loadModel('action')->create('build', $buildID, 'opened'); @@ -67,40 +66,36 @@ class build extends control return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('build', 'view', "buildID=$buildID") . "#app={$this->app->tab}")); } + $this->loadModel('product'); + $this->loadModel('project'); /* Set menu. */ - $executions = array(); if($this->app->tab == 'project') { - $this->loadModel('project')->setMenu($projectID); - $executions = $this->execution->getPairs($projectID, 'all', 'stagefilter'); - $executionID = empty($executionID) ? key($executions) : $executionID; + $this->project->setMenu($projectID); + $productGroups = $this->product->getProducts($projectID); + $branchGroups = $this->project->getBranchesByProject($projectID); $this->session->set('project', $projectID); } elseif($this->app->tab == 'execution') { - $execution = $this->execution->getByID($executionID); - $executions = $this->execution->getPairs($execution->project); - $projectID = $execution->project; + $execution = $this->execution->getByID($executionID); + $projectID = $execution->project; + $productGroups = $this->product->getProducts($executionID); + $branchGroups = $this->project->getBranchesByProject($executionID); $this->execution->setMenu($executionID); $this->session->set('project', $execution->project); } elseif($this->app->tab == 'qa') { - $execution = $this->execution->getByID($executionID); - $projectID = $execution ? $execution->project : 0; - $executions = $this->execution->getPairs($projectID); + $execution = $this->execution->getByID($executionID); + $projectID = $execution ? $execution->project : 0; + $productGroups = $this->product->getProducts($executionID); + $branchGroups = $this->project->getBranchesByProject($executionID); } $this->commonActions($projectID); - $executionList = empty($executions) ? array() : $this->execution->getByIdList(array_keys($executions)); - foreach($executionList as $execution) - { - if($execution->lifetime == 'ops') unset($executions[$execution->id]); - } - $productGroups = $this->loadModel('product')->getProducts($executionID); $productID = $productID ? $productID : key($productGroups); - $branchGroups = $this->loadModel('project')->getBranchesByProject($executionID); $branchPairs = $this->loadModel('branch')->getPairs($productID, 'active'); $branches = array(); $products = array(); @@ -124,7 +119,6 @@ class build extends control $this->view->executionID = $executionID; $this->view->products = $products; $this->view->projectID = $projectID; - $this->view->executions = $executions; $this->view->lastBuild = $this->build->getLast($executionID); $this->view->productGroups = $productGroups; $this->view->users = $this->user->getPairs('nodeleted|noclosed'); @@ -157,7 +151,7 @@ class build extends control $message = $this->executeHooks($buildID); if($message) $this->lang->saveSuccess = $message; - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('view', "buildID=$buildID"))); + return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('build', 'view', "buildID=$buildID") . "#app={$this->app->tab}")); } $this->loadModel('execution'); @@ -165,30 +159,26 @@ class build extends control $build = $this->build->getById((int)$buildID); /* Set menu. */ - if($this->app->tab == 'project') - { - $this->loadModel('project')->setMenu($build->project); - } - elseif($this->app->tab == 'execution') - { - $this->execution->setMenu($build->execution); - } + if($this->app->tab == 'project') $this->loadModel('project')->setMenu($build->project); + if($this->app->tab == 'execution') $this->execution->setMenu($build->execution); - /* Get stories and bugs. */ - $orderBy = 'status_asc, stage_asc, id_desc'; - - /* Assign. */ - $execution = $this->execution->getByID($build->execution); - if(empty($execution)) + $builds = array(); + if($build->execution) { - $execution = new stdclass(); - $execution->name = ''; + $productGroups = $this->product->getProducts($build->execution); + $branches = $this->loadModel('branch')->getList($build->product, $build->execution, 'all'); + } + else + { + $productGroups = $this->product->getProducts($build->project); + $branches = $this->loadModel('branch')->getList($build->product, $build->project, 'all'); + $builds = $this->build->getBuildPairs($build->product, $build->branch, 'noempty,notrunk,separate,noproject'); } $executions = $this->product->getExecutionPairsByProduct($build->product, $build->branch, 'id_desc', $this->session->project, 'stagefilter'); - if(!isset($executions[$build->execution])) $executions[$build->execution] = $execution->name; - $productGroups = $this->product->getProducts($build->execution); + /* Get stories and bugs. */ + $orderBy = 'status_asc, stage_asc, id_desc'; if(!isset($productGroups[$build->product])) { @@ -198,7 +188,6 @@ class build extends control } /* Display status of branch. */ - $branches = $this->loadModel('branch')->getList($build->product, $build->execution, 'all'); $branchTagOption = array(); foreach($branches as $branchInfo) { @@ -210,19 +199,18 @@ class build extends control $this->commonActions($build->project); - $this->view->title = $execution->name . $this->lang->colon . $this->lang->build->edit; - $this->view->position[] = html::a($this->createLink('execution', 'task', "executionID=$build->execution"), $execution->name); + $this->view->title = $build->name . $this->lang->colon . $this->lang->build->edit; $this->view->position[] = $this->lang->build->edit; $this->view->product = isset($productGroups[$build->product]) ? $productGroups[$build->product] : ''; $this->view->branchTagOption = $branchTagOption; - $this->view->executions = $executions; $this->view->orderBy = $orderBy; - - $this->view->productGroups = $productGroups; - $this->view->products = $products; - $this->view->users = $this->loadModel('user')->getPairs('noletter', $build->builder); - $this->view->build = $build; - $this->view->testtaskID = $this->dao->select('id')->from(TABLE_TESTTASK)->where('build')->eq($build->id)->andWhere('deleted')->eq(0)->fetch('id'); + $this->view->executions = $executions; + $this->view->productGroups = $productGroups; + $this->view->products = $products; + $this->view->users = $this->loadModel('user')->getPairs('noletter', $build->builder); + $this->view->build = $build; + $this->view->builds = $builds; + $this->view->testtaskID = $this->dao->select('id')->from(TABLE_TESTTASK)->where('build')->eq($build->id)->andWhere('deleted')->eq(0)->fetch('id'); $this->display(); } @@ -285,7 +273,6 @@ class build extends control $stages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->in(array_keys($stories))->andWhere('branch')->eq($build->branch)->fetchPairs('story', 'stage'); foreach($stages as $storyID => $stage) $stories[$storyID]->stage = $stage; - /* Set menu. */ if($this->app->tab == 'project') { @@ -310,13 +297,25 @@ class build extends control $this->executeHooks($buildID); - $buildIdList = $build->execution ? array() : array($buildID => $buildID); + if($this->app->tab == 'execution') + { + $objectType = 'execution'; + $objectID = $build->execution; + } + else + { + $objectType = 'project'; + $objectID = $build->project; + } + /* Assign. */ $this->view->canBeChanged = common::canBeChanged('build', $build); // Determines whether an object is editable. $this->view->users = $this->loadModel('user')->getPairs('noletter'); $this->view->build = $build; - $this->view->buildPairs = $this->build->getBuildPairs(0, 'all', 'noempty,notrunk', $build->execution, 'execution', $buildIdList); + $this->view->buildPairs = $this->build->getBuildPairs(0, 'all', 'noempty,notrunk', $objectID, $objectType); + $this->view->builds = $this->build->getByList(array_keys($this->view->buildPairs)); + $this->view->executions = $executions; $this->view->actions = $this->loadModel('action')->getList('build', $buildID); $this->view->link = $link; $this->view->param = $param; @@ -427,10 +426,11 @@ class build extends control * @param int $index the index of batch create bug. * @param bool $needCreate if need to append the link of create build * @param string $type get all builds or some builds belong to normal releases and executions are not done. + * @param string $extra * @access public * @return string */ - public function ajaxGetProjectBuilds($projectID, $productID, $varName, $build = '', $branch = 'all', $index = 0, $needCreate = false, $type = 'normal') + public function ajaxGetProjectBuilds($projectID, $productID, $varName, $build = '', $branch = 'all', $index = 0, $needCreate = false, $type = 'normal', $extra = '') { $isJsonView = $this->app->getViewType() == 'json'; if($varName == 'openedBuild') @@ -451,6 +451,12 @@ class build extends control if($isJsonView) return print(json_encode($builds)); return print(html::select($varName, $builds, $build, "class='form-control'")); } + + if(empty($projectID)) return $this->ajaxGetProductBuilds($productID, $varName, $build, $branch, $index, $type); + $builds = $this->build->getBuildPairs($productID, $branch, $type, $projectID, 'project', $build); + if(strpos($extra, 'multiple') !== false) $varName .= '[]'; + if($isJsonView) return print(json_encode($builds)); + return print(html::select($varName, $builds, $build, "class='form-control chosen' $extra")); } /** @@ -553,14 +559,11 @@ class build extends control $build = $this->build->getById($buildID); $product = $this->loadModel('product')->getById($build->product); - $this->loadModel('execution')->setMenu($build->execution); + if($build->execution) $this->loadModel('execution')->setMenu($build->execution); $this->loadModel('story'); $this->loadModel('tree'); $this->loadModel('product'); - $execution = $this->execution->getByID($build->execution); - if(empty($execution->hasProduct) and empty($execution->multiple)) unset($this->config->product->search['fields']['plan']); - /* Load pager. */ $this->app->loadClass('pager', $static = true); $pager = new pager($recTotal, $recPerPage, $pageID); @@ -579,7 +582,14 @@ class build extends control if($build->project) { $project = $this->loadModel('project')->getByID($build->project); - if(!$project->hasProduct and $project->model != 'scrum') unset($this->config->product->search['fields']['plan']); + if(!$project->hasProduct and $project->model != 'scrum') + { + unset($this->config->product->search['fields']['plan']); + } + else if(!$project->hasProduct and !$project->multiple) + { + unset($this->config->product->search['fields']['plan']); + } } if($product->type == 'normal') @@ -598,13 +608,14 @@ class build extends control } $this->loadModel('search')->setSearchParams($this->config->product->search); + $executionID = $build->execution ? $build->execution : $build->project; if($browseType == 'bySearch') { - $allStories = $this->story->getBySearch($build->product, $build->branch, $queryID, 'id', $build->execution, 'story', $build->stories, $pager); + $allStories = $this->story->getBySearch($build->product, $build->branch, $queryID, 'id', $executionID, 'story', $build->stories, $pager); } else { - $allStories = $this->story->getExecutionStories($build->execution, $build->product, 0, 't1.`order`_desc', 'byBranch', $build->branch, 'story', $build->stories, $pager); + $allStories = $this->story->getExecutionStories($executionID, $build->product, 0, 't1.`order`_desc', 'byBranch', $build->branch, 'story', $build->stories, $pager); } $this->view->allStories = $allStories; @@ -669,7 +680,7 @@ class build extends control /* Set menu. */ $build = $this->build->getByID($buildID); $product = $this->loadModel('product')->getByID($build->product); - $this->loadModel('execution')->setMenu($build->execution); + if($build->execution) $this->loadModel('execution')->setMenu($build->execution); /* Load pager. */ $this->app->loadClass('pager', $static = true); @@ -688,9 +699,6 @@ class build extends control $this->config->bug->search['params']['openedBuild']['values'] = $this->build->getBuildPairs($build->product, $branch = 'all', $params = ''); $this->config->bug->search['params']['resolvedBuild']['values'] = $this->config->bug->search['params']['openedBuild']['values']; - $execution = $this->execution->getByID($build->execution); - if(!$execution->hasProduct and !$execution->multiple) unset($this->config->bug->search['fields']['plan']); - unset($this->config->bug->search['fields']['product']); unset($this->config->bug->search['params']['product']); unset($this->config->bug->search['fields']['project']); @@ -699,7 +707,14 @@ class build extends control if($build->project) { $project = $this->loadModel('project')->getByID($build->project); - if(!$project->hasProduct and $project->model != 'scrum') unset($this->config->bug->search['fields']['plan']); + if(!$project->hasProduct and $project->model != 'scrum') + { + unset($this->config->bug->search['fields']['plan']); + } + else if(!$project->hasProduct and !$project->multiple) + { + unset($this->config->bug->search['fields']['plan']); + } } if($product->type == 'normal') @@ -717,13 +732,14 @@ class build extends control } $this->loadModel('search')->setSearchParams($this->config->bug->search); + $executionID = $build->execution ? $build->execution : $build->project; if($browseType == 'bySearch') { $allBugs = $this->bug->getBySearch($build->product, $build->branch, $queryID, 'id_desc', $build->bugs, $pager, $build->project); } else { - $allBugs = $this->bug->getExecutionBugs($build->execution, 0, 'all', $buildID, 'noclosed', 0, 'status_desc,id_desc', $build->bugs, $pager); + $allBugs = $this->bug->getExecutionBugs($executionID, 0, 'all', $buildID, 'noclosed', 0, 'status_desc,id_desc', $build->bugs, $pager); } $this->view->allBugs = $allBugs; diff --git a/module/build/js/common.js b/module/build/js/common.js index 599c9cba24..4dbf5d9cec 100644 --- a/module/build/js/common.js +++ b/module/build/js/common.js @@ -15,8 +15,8 @@ function loadBranches(productID) oldBranch = productGroups[productID]['branches']; } - executionID = currentTab == 'execution' ? executionID : $('#execution').val(); - $.get(createLink('branch', 'ajaxGetBranches', 'productID=' + productID + '&oldBranch=0¶m=active&projectID=' + executionID), function(data) + projectID = currentTab == 'execution' ? executionID : projectID; + $.get(createLink('branch', 'ajaxGetBranches', 'productID=' + productID + '&oldBranch=0¶m=active&projectID=' + projectID), function(data) { if(data) { @@ -25,3 +25,5 @@ function loadBranches(productID) } }); } + +function loadBranch() {return false;} diff --git a/module/build/js/create.js b/module/build/js/create.js index c4bd2f6865..836d749f35 100644 --- a/module/build/js/create.js +++ b/module/build/js/create.js @@ -5,37 +5,15 @@ $().ready(function() $('#name').val($(this).text()).focus(); }); -}); -/** - * Load products. - * - * @param int $executionID - * @access public - * @return void - */ -function loadProducts(executionID) -{ - $('#product').remove(); - $('#product_chosen').remove(); - $('#branch').remove(); - $('#branch_chosen').remove(); - $('#noProduct').remove(); - $.get(createLink('product', 'ajaxGetProducts', 'executionID=' + executionID), function(data) + $(document).on('change', '#product, #branch', function() { - if(data) + var productID = $('#product').val(); + var branch = $('#branch').val(); + $.get(createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=builds&build=&branch=' + branch + '&index=&needCreate=&type=noempty,notrunk,separate,noproject&extra=multiple'), function(data) { - if(data.indexOf("required") != -1) - { - $('#productBox').addClass('required'); - } - else - { - $('#productBox').removeClass('required'); - } - - $('#productBox').append(data); - $('#product').chosen(); - loadBranches($("#product").val()); - } + if(data) $('#buildBox').html(data); + $('#builds').chosen(); + }); }); -} + $('#product').change(); +}); diff --git a/module/build/js/edit.js b/module/build/js/edit.js index 9c4a7505b0..51703ab620 100644 --- a/module/build/js/edit.js +++ b/module/build/js/edit.js @@ -1,6 +1,26 @@ -var oldExecutionID = $('#execution').val(); +$().ready(function() +{ + var oldExecutionID = $('#execution').val(); + $(document).on('change', '#product, #branch', function() + { + if(executionID) + { + loadExecutions(oldExecutionID); + } + else + { + var productID = $('#product').val(); + var branch = $('#branch').val(); + $.get(createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=builds&build=&branch=' + branch + '&index=&needCreate=&type=noempty,notrunk,separate,noproject&extra=multiple'), function(data) + { + if(data) $('#buildBox').html(data); + $('#builds').chosen(); + }); + } + }); +}); -function loadExecutions() +function loadExecutions(oldExecutionID) { var productID = $('#product').val(); var branchID = $('#branch').length > 0 ? $('#branch').val() : 0; @@ -9,8 +29,3 @@ function loadExecutions() $('#executionsBox #execution').chosen().removeAttr('onchange'); }); } - -$(document).on('change', '#product,#branch', function() -{ - loadExecutions(); -}) diff --git a/module/build/js/view.js b/module/build/js/view.js index 1a258ee2ca..6ed6c1032d 100644 --- a/module/build/js/view.js +++ b/module/build/js/view.js @@ -2,7 +2,7 @@ function showLink(buildID, type, param) { var method = type == 'story' ? 'linkStory' : 'linkBug'; if(typeof(param) == 'undefined') param = '&browseType=' + type + '¶m=0'; - loadURL(createLink('build', method, 'buildID=' + buildID + param), type); + loadURL(createLink(currentModule, method, 'buildID=' + buildID + param), type); $('.actions').find("a[href*='" + type + "']").addClass('hidden'); } diff --git a/module/build/lang/de.php b/module/build/lang/de.php index d714465ad7..102f2a399f 100644 --- a/module/build/lang/de.php +++ b/module/build/lang/de.php @@ -33,6 +33,7 @@ $lang->build->project = 'Project'; $lang->build->branch = 'Platform/Branch'; $lang->build->branchName = '%s'; $lang->build->execution = $lang->executionCommon; +$lang->build->builds = 'Linked Builds'; $lang->build->name = 'Name'; $lang->build->date = 'Datum'; $lang->build->builder = 'Builder'; @@ -55,6 +56,7 @@ $lang->build->emptyExecution = $lang->executionCommon . 'should be not empty.'; $lang->build->notice = new stdclass(); $lang->build->notice->changeProduct = "The {$lang->SRCommon}, bug, or the version of the submitted test order has been linked, and its {$lang->productCommon} cannot be modified"; $lang->build->notice->changeExecution = "The version of the submitted test order cannot be modified {$lang->executionCommon}"; +$lang->build->notice->changeBuilds = "The version of the submitted test order cannot be modified builds"; $lang->build->finishStories = " %s {$lang->SRCommon} sind abgeschlossen."; $lang->build->resolvedBugs = ' %s Bugs sind gelöst.'; diff --git a/module/build/lang/en.php b/module/build/lang/en.php index 73ec7a1d33..2cad705a92 100644 --- a/module/build/lang/en.php +++ b/module/build/lang/en.php @@ -33,6 +33,7 @@ $lang->build->project = 'Project'; $lang->build->branch = 'Platform/Branch'; $lang->build->branchName = '%s'; $lang->build->execution = $lang->executionCommon; +$lang->build->builds = 'Linked Builds'; $lang->build->name = 'Name'; $lang->build->date = 'Date'; $lang->build->builder = 'Builder'; @@ -55,6 +56,7 @@ $lang->build->emptyExecution = $lang->executionCommon . 'should be not empty.'; $lang->build->notice = new stdclass(); $lang->build->notice->changeProduct = "The {$lang->SRCommon}, bug, or the version of the submitted test order has been linked, and its {$lang->productCommon} cannot be modified"; $lang->build->notice->changeExecution = "The version of the submitted test order cannot be modified {$lang->executionCommon}"; +$lang->build->notice->changeBuilds = "The version of the submitted test order cannot be modified builds"; $lang->build->finishStories = " Finished {$lang->SRCommon} %s"; $lang->build->resolvedBugs = ' Resolved Bug %s'; diff --git a/module/build/lang/fr.php b/module/build/lang/fr.php index 48326670ad..5bf136f09b 100644 --- a/module/build/lang/fr.php +++ b/module/build/lang/fr.php @@ -33,6 +33,7 @@ $lang->build->project = 'Project'; $lang->build->branch = 'Plateforme/Branche'; $lang->build->branchName = '%s'; $lang->build->execution = $lang->executionCommon; +$lang->build->builds = 'Linked Builds'; $lang->build->name = 'Nom'; $lang->build->date = 'Date'; $lang->build->builder = 'Builder'; @@ -55,6 +56,7 @@ $lang->build->emptyExecution = $lang->executionCommon . 'should be not empty.'; $lang->build->notice = new stdclass(); $lang->build->notice->changeProduct = "The {$lang->SRCommon}, bug, or the version of the submitted test order has been linked, and its {$lang->productCommon} cannot be modified"; $lang->build->notice->changeExecution = "The version of the submitted test order cannot be modified {$lang->executionCommon}"; +$lang->build->notice->changeBuilds = "The version of the submitted test order cannot be modified builds"; $lang->build->finishStories = " {$lang->SRCommon} Terminées %s"; $lang->build->resolvedBugs = ' Bugs Résolus %s'; diff --git a/module/build/lang/vi.php b/module/build/lang/vi.php index e0080ba5da..c5a6f474d1 100644 --- a/module/build/lang/vi.php +++ b/module/build/lang/vi.php @@ -31,6 +31,7 @@ $lang->build->id = 'ID'; $lang->build->product = $lang->productCommon; $lang->build->branch = 'Platform/Branch'; $lang->build->execution = $lang->executionCommon; +$lang->build->builds = 'Linked Builds'; $lang->build->name = 'Tên'; $lang->build->date = 'Ngày'; $lang->build->builder = 'Builder'; @@ -51,6 +52,7 @@ $lang->build->noBuild = 'Không có bản dựng nào'; $lang->build->notice = new stdclass(); $lang->build->notice->changeProduct = "The {$lang->SRCommon}, bug, or the version of the submitted test order has been linked, and its {$lang->productCommon} cannot be modified"; $lang->build->notice->changeExecution = "The version of the submitted test order cannot be modified {$lang->executionCommon}"; +$lang->build->notice->changeBuilds = "The version of the submitted test order cannot be modified builds"; $lang->build->finishStories = " {$lang->SRCommon} đã kết thúc %s"; $lang->build->resolvedBugs = ' Bug đã giải quyết %s'; diff --git a/module/build/lang/zh-cn.php b/module/build/lang/zh-cn.php index c45863af1a..b1bbf9d7ca 100644 --- a/module/build/lang/zh-cn.php +++ b/module/build/lang/zh-cn.php @@ -33,6 +33,7 @@ $lang->build->project = '所属项目'; $lang->build->branch = '平台/分支'; $lang->build->branchName = '所属%s'; $lang->build->execution = '所属' . $lang->executionCommon; +$lang->build->builds = '关联版本'; $lang->build->name = '名称编号'; $lang->build->date = '打包日期'; $lang->build->builder = '构建者'; @@ -55,6 +56,7 @@ $lang->build->emptyExecution = $lang->executionCommon . '不能为空。'; $lang->build->notice = new stdclass(); $lang->build->notice->changeProduct = "已经关联{$lang->SRCommon}、Bug或提交测试单的版本,不能修改其所属{$lang->productCommon}"; $lang->build->notice->changeExecution = "提交测试单的版本,不能修改其所属{$lang->executionCommon}"; +$lang->build->notice->changeBuilds = "提交测试单的版本,不能修改关联版本"; $lang->build->finishStories = " 本次共完成 %s 个{$lang->SRCommon}"; $lang->build->resolvedBugs = ' 本次共解决 %s 个Bug'; diff --git a/module/build/model.php b/module/build/model.php index 5d563e01cb..2ff01927dd 100644 --- a/module/build/model.php +++ b/module/build/model.php @@ -225,19 +225,20 @@ class buildModel extends model ->fetchPairs(); } + $branchs = strpos($params, 'separate') === false ? "0,$branch" : $branch; $allBuilds = $this->dao->select('t1.id, t1.name, t1.deleted, t2.status as objectStatus, t3.id as releaseID, t3.status as releaseStatus, t4.name as branchName, t5.type as productType')->from(TABLE_BUILD)->alias('t1') ->beginIF($objectType === 'execution')->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')->fi() ->beginIF($objectType === 'project')->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')->fi() - ->leftJoin(TABLE_RELEASE)->alias('t3')->on('t1.id = t3.build') + ->leftJoin(TABLE_RELEASE)->alias('t3')->on("FIND_IN_SET(t1.id,t3.build)") ->leftJoin(TABLE_BRANCH)->alias('t4')->on('t1.branch = t4.id') ->leftJoin(TABLE_PRODUCT)->alias('t5')->on('t1.product = t5.id') ->where(1)->eq(1) ->beginIF(strpos($params, 'hasDeleted') === false)->andWhere('t1.deleted')->eq(0)->fi() + ->beginIF(strpos($params, 'noproject') !== false)->andWhere('t1.execution')->ne(0)->fi() ->beginIF($products)->andWhere('t1.product')->in($products)->fi() ->beginIF($objectType === 'execution' and $objectID)->andWhere('t1.execution')->eq($objectID)->fi() ->beginIF($objectType === 'project' and $objectID)->andWhere('t1.project')->eq($objectID)->fi() - ->beginIF($objectType === 'execution' and !$objectID)->andWhere('t1.id')->in($buildIdList)->fi() - ->beginIF($branch !== 'all')->andWhere('t1.branch')->in("0,$branch")->fi() + ->beginIF($branch !== 'all')->andWhere('t1.branch')->in("$branchs")->fi() ->orderBy('t1.date desc, t1.id desc')->fetchAll('id'); /* Set builds and filter done executions and terminate releases. */ @@ -247,6 +248,7 @@ class buildModel extends model { if(empty($build->releaseID) and (strpos($params, 'nodone') !== false) and ($build->objectStatus === 'done')) continue; if((strpos($params, 'noterminate') !== false) and ($build->releaseStatus === 'terminate')) continue; + if($build->deleted == 1) $build->name .= ' (' . $this->lang->build->deleted . ')'; $branchName = $build->branchName ? $build->branchName : $this->lang->branch->main; @@ -262,7 +264,7 @@ class buildModel extends model $releases = $this->dao->select('build, name')->from(TABLE_RELEASE) ->where('build')->in(array_keys($builds)) ->andWhere('product')->in($products) - ->beginIF($branch !== 'all')->andWhere('branch')->in("0,$branch")->fi() + ->beginIF($branch !== 'all')->andWhere('branch')->in("$branchs")->fi() ->andWhere('deleted')->eq(0) ->fetchPairs(); foreach($releases as $buildID => $releaseName) @@ -299,26 +301,33 @@ class buildModel extends model * Create a build * * @param int $executionID + * @param int $projectID * @access public * @return void */ - public function create($executionID) + public function create($executionID = 0, $projectID = 0) { $build = new stdclass(); $build->stories = ''; $build->bugs = ''; - $execution = $this->loadModel('execution')->getByID($executionID); + if($projectID) + { + $project = $this->loadModel('project')->getByID($projectID); + if(!$project->multiple) $executionID = $this->dao->select('id')->from(TABLE_EXECUTION)->where('project')->eq($projectID)->fetch('id'); + } $build = fixer::input('post') - ->setDefault('project', $execution->project) + ->setDefault('project', $projectID) ->setDefault('product', 0) ->setDefault('branch', 0) + ->setDefault('builds', '') ->cleanInt('product,branch') - ->add('execution', (int)$executionID) + ->add('execution', $executionID) ->add('createdBy', $this->app->user->account) ->add('createdDate', helper::now()) ->stripTags($this->config->build->editor->create['id'], $this->config->allowedTags) + ->join('builds', ',') ->remove('resolvedBy,allchecker,files,labels,uid') ->get(); @@ -355,7 +364,9 @@ class buildModel extends model ->add('id', $buildID) ->setIF(!isset($_POST['branch']), 'branch', $oldBuild->branch) ->setDefault('product', $oldBuild->product) + ->setDefault('builds', '') ->cleanInt('product,branch,execution') + ->join('builds', ',') ->remove('allchecker,resolvedBy,files,labels,uid') ->get(); @@ -565,21 +576,22 @@ class buildModel extends model global $app; $tab = $app->tab; + $module = $build->execution ? 'build' : 'projectbuild'; if($type == 'browse') { $executionID = $tab == 'execution' ? $extraParams['executionID'] : $build->execution; $execution = $this->loadModel('execution')->getByID($executionID); $testtaskApp = (!empty($execution->type) and $execution->type == 'kanban') ? 'data-app="qa"' : "data-app='{$tab}'"; - if(common::hasPriv('build', 'linkstory') and common::canBeChanged('build', $build)) $menu .= $this->buildMenu('build', 'view', "{$params}&type=story&link=true", $build, $type, 'link', '', '', '', "data-app={$tab}", $this->lang->build->linkStory); + if(common::hasPriv($module, 'linkstory') and common::canBeChanged('build', $build)) $menu .= $this->buildMenu($module, 'view', "{$params}&type=story&link=true", $build, $type, 'link', '', '', '', "data-app={$tab}", $this->lang->build->linkStory); $menu .= $this->buildMenu('testtask', 'create', "product=$build->product&execution={$executionID}&build=$build->id&projectID=$build->project", $build, $type, 'bullhorn', '', '', '', $testtaskApp); if($tab == 'execution' and !empty($execution->type) and $execution->type != 'kanban') $menu .= $this->buildMenu('execution', 'bug', "execution={$extraParams['executionID']}&productID={$extraParams['productID']}&branchID=all&orderBy=status&build=$build->id", $build, $type, '', '', '', '', $this->lang->execution->viewBug); - if($tab == 'project' or empty($execution->type) or $execution->type == 'kanban') $menu .= $this->buildMenu('build', 'view', "{$params}&type=generatedBug", $build, $type, 'bug', '', '', '', "data-app='$tab'", $this->lang->project->bug); + if($tab == 'project' or empty($execution->type) or $execution->type == 'kanban') $menu .= $this->buildMenu($module, 'view', "{$params}&type=generatedBug", $build, $type, 'bug', '', '', '', "data-app='$tab'", $this->lang->project->bug); - $menu .= $this->buildMenu('build', 'edit', $params, $build, $type); - $menu .= $this->buildMenu('build', 'delete', $params, $build, $type, 'trash', 'hiddenwin'); + $menu .= $this->buildMenu($module, 'edit', $params, $build, $type); + $menu .= $this->buildMenu($module, 'delete', $params, $build, $type, 'trash', 'hiddenwin'); } else { @@ -588,10 +600,10 @@ class buildModel extends model $menu .= $this->buildFlowMenu('build', $build, 'view', 'direct'); - $editClickable = $this->buildMenu('build', 'edit', $params, $build, $type, '', '', '', '', '', '', false); - $deleteClickable = $this->buildMenu('build', 'delete', $params, $build, $type, '', '', '', '', '', '', false); - if(common::hasPriv('build', 'edit') and $editClickable) $menu .= html::a(helper::createLink('build', 'edit', $params), " " . $this->lang->edit, '', "class='btn btn-link' title='{$this->lang->build->edit}' data-app='{$app->tab}'"); - if(common::hasPriv('build', 'delete') and $deleteClickable) $menu .= html::a(helper::createLink('build', 'delete', $params), " " . $this->lang->delete, '', "class='btn btn-link' title='{$this->lang->build->delete}' target='hiddenwin' data-app='{$app->tab}'"); + $editClickable = $this->buildMenu($module, 'edit', $params, $build, $type, '', '', '', '', '', '', false); + $deleteClickable = $this->buildMenu($module, 'delete', $params, $build, $type, '', '', '', '', '', '', false); + if(common::hasPriv($module, 'edit') and $editClickable) $menu .= html::a(helper::createLink($module, 'edit', $params), " " . $this->lang->edit, '', "class='btn btn-link' title='{$this->lang->build->edit}' data-app='{$app->tab}'"); + if(common::hasPriv($module, 'delete') and $deleteClickable) $menu .= html::a(helper::createLink($module, 'delete', $params), " " . $this->lang->delete, '', "class='btn btn-link' title='{$this->lang->build->delete}' target='hiddenwin' data-app='{$app->tab}'"); } return $menu; diff --git a/module/build/view/create.html.php b/module/build/view/create.html.php index afec7286f9..3297178af4 100644 --- a/module/build/view/create.html.php +++ b/module/build/view/create.html.php @@ -19,12 +19,6 @@
- tab == 'project'):?> - - - - - @@ -48,6 +42,12 @@ + tab == 'project' && !empty($multipleProject)):?> + + + + + @@ -93,6 +93,7 @@ +app->tab);?> diff --git a/module/build/view/edit.html.php b/module/build/view/edit.html.php index 1226794642..8fc810e5aa 100644 --- a/module/build/view/edit.html.php +++ b/module/build/view/edit.html.php @@ -23,7 +23,7 @@
executionCommon;?>
build->product;?>
build->builds;?>
build->name;?>
- + - + + execution):?> - - - - + + + + + + + @@ -91,6 +95,7 @@ project)?> +builds)?> execution)?> app->tab);?> diff --git a/module/build/view/view.html.php b/module/build/view/view.html.php index 1229bac8a3..70f09e694a 100644 --- a/module/build/view/view.html.php +++ b/module/build/view/view.html.php @@ -35,8 +35,9 @@ tbody tr td:first-child input {display: none;} echo "'; @@ -53,6 +54,7 @@ tbody tr td:first-child input {display: none;} +execution ? 'build' : 'projectbuild';?>
@@ -64,13 +66,13 @@ tbody tr td:first-child input {display: none;}
' id='stories'> - +
id, \"story\")", ' ' . $lang->build->linkStory, '', "class='btn btn-primary'");?>
- id}")?>' id='linkedStoriesForm'> + id}")?>' id='linkedStoriesForm'>
build->product;?> build->notice->changeProduct;?>
build->execution;?>execution, "class='form-control chosen' required $disabled");?>build->notice->changeExecution;?>build->builds;?>builds, "class='form-control chosen' multiple $disabled");?>build->notice->changeBuilds;?>
build->execution;?>execution, "class='form-control chosen' required $disabled");?>build->notice->changeExecution;?>
build->name;?>
- + id}&type=story&link=$link¶m=$param&orderBy=%s";?> @@ -133,9 +135,9 @@ tbody tr td:first-child input {display: none;} - + diff --git a/module/release/view/edit.html.php b/module/release/view/edit.html.php index a72643b371..d203e03e18 100644 --- a/module/release/view/edit.html.php +++ b/module/release/view/edit.html.php @@ -37,7 +37,7 @@ - + diff --git a/module/release/view/view.html.php b/module/release/view/view.html.php index ba37f91c67..dbb2394344 100644 --- a/module/release/view/view.html.php +++ b/module/release/view/view.html.php @@ -351,8 +351,16 @@ - @@ -379,13 +387,19 @@ { echo $this->fetch('file', 'printFiles', array('files' => $release->files, 'fieldset' => 'false', 'object' => $release, 'method' => 'view', 'showDelete' => false)); } - elseif($release->filePath) + if($release->builds) { - echo $lang->release->filePath . html::a($release->filePath, $release->filePath, '_blank'); - } - elseif($release->scmPath) - { - echo $lang->release->scmPath . html::a($release->scmPath, $release->scmPath, '_blank'); + foreach($release->builds as $build) + { + if($build->filePath) + { + echo $lang->release->filePath . html::a($release->filePath, $release->filePath, '_blank'); + } + elseif($build->scmPath) + { + echo $lang->release->scmPath . html::a($release->scmPath, $release->scmPath, '_blank'); + } + } } ?> diff --git a/module/search/model.php b/module/search/model.php index fe374390ec..fb686c62ae 100644 --- a/module/search/model.php +++ b/module/search/model.php @@ -789,7 +789,8 @@ class searchModel extends model } elseif($module == 'story' or $module == 'requirement') { - $story = $objectList[$module][$record->objectID]; + $story = $objectList[$module][$record->objectID]; + $module = 'story'; if(!empty($story->lib)) { $module = 'assetlib'; diff --git a/module/story/config.php b/module/story/config.php index 8271730085..e5003141eb 100644 --- a/module/story/config.php +++ b/module/story/config.php @@ -139,7 +139,7 @@ $config->story->datatable->fieldList['assignedDate']['required'] = 'no'; $config->story->datatable->fieldList['product']['title'] = 'product'; $config->story->datatable->fieldList['product']['control'] = 'hidden'; -$config->story->datatable->fieldList['product']['dataSource'] = array('module' => 'port', 'method' => 'getRelatedObjects', 'params' => 'story&product&id,name'); +$config->story->datatable->fieldList['product']['dataSource'] = array('module' => 'transfer', 'method' => 'getRelatedObjects', 'params' => 'story&product&id,name'); $config->story->datatable->fieldList['branch']['title'] = 'branch'; $config->story->datatable->fieldList['branch']['fixed'] = 'no'; diff --git a/module/story/control.php b/module/story/control.php index 5d73f07483..269934683f 100755 --- a/module/story/control.php +++ b/module/story/control.php @@ -1218,8 +1218,26 @@ class story extends control } if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success')); - $params = $module == 'story' ? "storyID=$storyID&version=0¶m=0&storyType=$storyType" : "storyID=$storyID"; - return print(js::locate($this->createLink($module, 'view', $params), 'parent')); + + if($this->app->tab == 'project') + { + $module = 'projectstory'; + $method = 'view'; + $params = "storyID=$storyID"; + } + elseif($this->app->tab == 'execution') + { + $module = 'execution'; + $method = 'storyView'; + $params = "storyID=$storyID"; + } + else + { + $module = 'story'; + $method = 'view'; + $params = "storyID=$storyID&version=0¶m=0&storyType=$storyType"; + } + return print(js::locate($this->createLink($module, $method, $params), 'parent')); } $this->commonAction($storyID); @@ -1541,10 +1559,27 @@ class story extends control } } - $module = $from == 'project' ? 'projectstory' : 'story'; if(defined('RUN_MODE') and RUN_MODE == 'api') return $this->send(array('status' => 'success', 'data' => $storyID)); - $params = $module == 'story' ? "storyID=$storyID&version=0¶m=0&storyType=$storyType" : "storyID=$storyID"; - return print(js::locate($this->createLink($module, 'view', $params), 'parent')); + + if($from == 'project') + { + $module = 'projectstory'; + $method = 'view'; + $params = "storyID=$storyID"; + } + elseif($from == 'execution') + { + $module = 'execution'; + $method = 'storyView'; + $params = "storyID=$storyID"; + } + else + { + $module = 'story'; + $method = 'view'; + $params = "storyID=$storyID&version=0¶m=0&storyType=$storyType"; + } + return print(js::locate($this->createLink($module, $method, $params), 'parent')); } /* Get story and product. */ @@ -1647,7 +1682,28 @@ class story extends control $action = $story->status == 'changing' ? 'recalledChange' : 'Recalled'; $this->loadModel('action')->create('story', $storyID, $action); - if($from == 'view') return print(js::locate($this->createLink('story', 'view', "storyID=$storyID&version=0¶m=0&storyType=$storyType"), 'parent')); + if($from == 'view') + { + if($this->app->tab == 'project') + { + $module = 'projectstory'; + $method = 'view'; + $params = "storyID=$storyID"; + } + elseif($this->app->tab == 'execution') + { + $module = 'execution'; + $method = 'storyView'; + $params = "storyID=$storyID"; + } + else + { + $module = 'story'; + $method = 'view'; + $params = "storyID=$storyID&version=0¶m=0&storyType=$storyType"; + } + return print(js::locate($this->createLink($module, $method, $params), 'parent')); + } $locateLink = $this->session->storyList ? $this->session->storyList : $this->createLink('product', 'browse', "productID={$story->product}"); return print(js::locate($locateLink, 'parent')); @@ -2706,7 +2762,7 @@ class story extends control /* format the fields of every story in order to export data. */ if($_POST) { - $this->session->set('storyPortParams', array('productID' => $productID, 'executionID' => $executionID)); + $this->session->set('storyTransferParams', array('productID' => $productID, 'executionID' => $executionID)); /* Create field lists. */ if(!$productID) { @@ -2723,7 +2779,7 @@ class story extends control } $this->post->set('rows', $this->story->getExportStorys($executionID, $orderBy)); - $this->fetch('port', 'export', 'model=story'); + $this->fetch('transfer', 'export', 'model=story'); } $fileName = $storyType == 'requirement' ? $this->lang->URCommon : $this->lang->SRCommon; diff --git a/module/story/css/view.de.css b/module/story/css/view.de.css index 921999def9..252cc2d4b7 100644 --- a/module/story/css/view.de.css +++ b/module/story/css/view.de.css @@ -1 +1 @@ -#legendRelated .table-data tbody>tr>th {width: 125px;} +#legendRelated .table-data tbody>tr>th, #legendLifeTime .table-data tbody>tr>th {width: 125px;} diff --git a/module/story/css/view.fr.css b/module/story/css/view.fr.css index 9d1a837d3b..3c51922daa 100644 --- a/module/story/css/view.fr.css +++ b/module/story/css/view.fr.css @@ -1 +1 @@ -#legendRelated .table-data tbody>tr>th {width: 95px;} +#legendRelated .table-data tbody>tr>th, #legendBasicInfo .table-data tbody>tr>th, #legendLifeTime .table-data tbody>tr>th {width: 96px !important;} diff --git a/module/story/js/batchclose.js b/module/story/js/batchclose.js index 7c15a5c040..bd33321844 100755 --- a/module/story/js/batchclose.js +++ b/module/story/js/batchclose.js @@ -12,16 +12,22 @@ function setDuplicateAndChild(resolution, storyID) { $('#childStoryBox' + storyID).hide(); $('#duplicateStoryBox' + storyID).show(); + $('#duplicateStoryTitle').show(); + $('.form-actions').attr('colspan', 6); } else if(resolution == 'subdivided') { $('#duplicateStoryBox' + storyID).hide(); $('#childStoryBox' + storyID).show(); + $('#duplicateStoryTitle').hide(); + $('.form-actions').attr('colspan', 5); } else { $('#duplicateStoryBox' + storyID).hide(); $('#childStoryBox' + storyID).hide(); + $('#duplicateStoryTitle').hide(); + $('.form-actions').attr('colspan', 5); } } diff --git a/module/story/lang/de.php b/module/story/lang/de.php index 463c9ef52e..95cd26c1ff 100644 --- a/module/story/lang/de.php +++ b/module/story/lang/de.php @@ -297,7 +297,7 @@ $lang->story->mustChooseResult = 'Ergebnis wählen'; $lang->story->mustChoosePreVersion = 'Version wählen um es umzukhren.'; $lang->story->noStory = 'Keine Storys. '; $lang->story->noRequirement = 'No Requirements'; -$lang->story->ignoreChangeStage = 'Story %s is in Draft or Closed status. Please review it..'; +$lang->story->ignoreChangeStage = 'The status of %s is Draft or Closed. This operation has been filtered.'; $lang->story->cannotDeleteParent = "Can not delete parent {$lang->SRCommon}"; $lang->story->moveChildrenTips = "Its Child {$lang->SRCommon} will be moved to the selected product when editing the linked product of Parent {$lang->SRCommon}."; $lang->story->changeTips = 'The story associated with the requirements to change, click "Cancel" ignore this change, click "Confirm" to change the story.'; diff --git a/module/story/lang/en.php b/module/story/lang/en.php index 48f452a2cf..141a56b403 100644 --- a/module/story/lang/en.php +++ b/module/story/lang/en.php @@ -297,7 +297,7 @@ $lang->story->mustChooseResult = 'Select Result'; $lang->story->mustChoosePreVersion = 'Select a version to revert to.'; $lang->story->noStory = 'No stories yet. '; $lang->story->noRequirement = 'No requirements yet. '; -$lang->story->ignoreChangeStage = 'Story %s is in Draft or Closed status. Please review it..'; +$lang->story->ignoreChangeStage = 'The status of %s is Draft or Closed. This operation has been filtered.'; $lang->story->cannotDeleteParent = "Can not delete parent {$lang->SRCommon}"; $lang->story->moveChildrenTips = "Its Child {$lang->SRCommon} will be moved to the selected product when editing the linked product of Parent {$lang->SRCommon}."; $lang->story->changeTips = 'The story associated with the requirements to change, click "Cancel" ignore this change, click "Confirm" to change the story.'; diff --git a/module/story/lang/fr.php b/module/story/lang/fr.php index 8a0d3b353a..a3680cc69a 100644 --- a/module/story/lang/fr.php +++ b/module/story/lang/fr.php @@ -297,7 +297,7 @@ $lang->story->mustChooseResult = 'Sélect Résultat'; $lang->story->mustChoosePreVersion = 'Sélect une version pour revenir en arrière.'; $lang->story->noStory = "Aucune story pour l'instant. "; $lang->story->noRequirement = 'No Requirements'; -$lang->story->ignoreChangeStage = 'Story %s is in Draft or Closed status. Please review it..'; +$lang->story->ignoreChangeStage = 'The status of %s is Draft or Closed. This operation has been filtered.'; $lang->story->cannotDeleteParent = "Impossible de supprimer {$lang->SRCommon} parent"; $lang->story->moveChildrenTips = "Its Child {$lang->SRCommon} will be moved to the selected product when editing the linked product of Parent {$lang->SRCommon}."; $lang->story->changeTips = 'The story associated with the requirements to change, click "Cancel" ignore this change, click "Confirm" to change the story.'; diff --git a/module/story/lang/vi.php b/module/story/lang/vi.php index 56d2c8f436..cf6524f8ae 100644 --- a/module/story/lang/vi.php +++ b/module/story/lang/vi.php @@ -289,7 +289,7 @@ $lang->story->mustChooseResult = 'Chọn kết quả'; $lang->story->mustChoosePreVersion = 'Chọn một phiên bản để chuyển thành.'; $lang->story->noStory = 'Không có câu chuyện nào'; $lang->story->noRequirement = 'Không có câu chuyện nào'; -$lang->story->ignoreChangeStage = 'Story %s is in Draft or Closed status. Please review it..'; +$lang->story->ignoreChangeStage = 'The status of %s is Draft or Closed. This operation has been filtered.'; $lang->story->cannotDeleteParent = "Không thể xóa {$lang->SRCommon} mẹ"; $lang->story->moveChildrenTips = "Its Child {$lang->SRCommon} will be moved to the selected product when editing the linked product of Parent {$lang->SRCommon}."; $lang->story->changeTips = 'The story associated with the requirements to change, click "Cancel" ignore this change, click "Confirm" to change the story.'; diff --git a/module/story/lang/zh-cn.php b/module/story/lang/zh-cn.php index 20493d1563..e3a8dbd10e 100644 --- a/module/story/lang/zh-cn.php +++ b/module/story/lang/zh-cn.php @@ -297,7 +297,7 @@ $lang->story->mustChooseResult = '必须选择评审意见'; $lang->story->mustChoosePreVersion = '必须选择回溯的版本'; $lang->story->noStory = "暂时没有{$lang->SRCommon}。"; $lang->story->noRequirement = "暂时没有{$lang->URCommon}。"; -$lang->story->ignoreChangeStage = "{$lang->SRCommon} %s 为草稿状态或已关闭状态,没有修改其阶段。"; +$lang->story->ignoreChangeStage = "{$lang->SRCommon} %s 状态为草稿或已关闭,本次操作已被过滤。"; $lang->story->cannotDeleteParent = "不能删除父{$lang->SRCommon}"; $lang->story->moveChildrenTips = "修改父{$lang->SRCommon}的所属产品会将其下的子{$lang->SRCommon}也移动到所选产品下。"; $lang->story->changeTips = '该软件需求关联的用户需求有变更,点击“不变更”忽略此条变更,点击“变更”来进行该软件需求的变更。'; diff --git a/module/story/lang/zh-tw.php b/module/story/lang/zh-tw.php index 68027736c7..d6882f57d3 100644 --- a/module/story/lang/zh-tw.php +++ b/module/story/lang/zh-tw.php @@ -275,7 +275,7 @@ $lang->story->mustChooseResult = '必須選擇評審意見'; $lang->story->mustChoosePreVersion = '必須選擇回溯的版本'; $lang->story->noStory = "暫時沒有{$lang->SRCommon}。"; $lang->story->noRequirement = "暫時沒有{$lang->URCommon}。"; -$lang->story->ignoreChangeStage = "{$lang->SRCommon} %s 為草稿狀態或已關閉狀態,沒有修改其階段。"; +$lang->story->ignoreChangeStage = "{$lang->SRCommon} %s 為草稿狀態或已關閉狀態,本次操作已被過濾。"; $lang->story->cannotDeleteParent = "不能刪除父{$lang->SRCommon}"; $lang->story->moveChildrenTips = "修改父{$lang->SRCommon}的所屬產品會將其下的子{$lang->SRCommon}也移動到所選產品下。"; $lang->story->changeTips = '該軟件需求關聯的用戶需求有變更,點擊“不變更”忽略此條變更,點擊“變更”來進行該軟件需求的變更。'; diff --git a/module/story/model.php b/module/story/model.php index 11578f6b9d..7b2e1e662d 100644 --- a/module/story/model.php +++ b/module/story/model.php @@ -521,6 +521,7 @@ class storyModel extends model $story->category = $stories->category[$i]; $story->pri = $stories->pri[$i]; $story->estimate = $stories->estimate[$i]; + $story->spec = $stories->spec[$i]; $story->status = $saveDraft ? 'draft' : ((empty($stories->reviewer[$i]) and !$forceReview) ? 'active' : 'reviewing'); $story->stage = ($this->app->tab == 'project' or $this->app->tab == 'execution') ? 'projected' : 'wait'; $story->keywords = $stories->keywords[$i]; @@ -558,7 +559,7 @@ class storyModel extends model $link2Plans = array(); foreach($data as $i => $story) { - $this->dao->insert(TABLE_STORY)->data($story)->autoCheck()->checkFlow()->exec(); + $this->dao->insert(TABLE_STORY)->data($story, 'spec')->autoCheck()->checkFlow()->exec(); if(dao::isError()) { echo js::error(dao::getError()); @@ -2743,7 +2744,7 @@ class storyModel extends model * @param int $limit * @param string $type * @param string $storyType requirement|story - * @param bool $hasParent + * @param bool|string $hasParent * @access public * @return array */ @@ -2755,7 +2756,7 @@ class storyModel extends model ->beginIF($productID)->andWhere('t1.product')->in($productID)->fi() ->beginIF($moduleIdList)->andWhere('t1.module')->in($moduleIdList)->fi() ->beginIF($branch !== 'all')->andWhere('t1.branch')->in("0,$branch")->fi() - ->beginIF(!$hasParent)->andWhere('t1.parent')->ge(0)->fi() + ->beginIF(!$hasParent or $hasParent == 'false')->andWhere('t1.parent')->ge(0)->fi() ->beginIF($status and $status != 'all')->andWhere('t1.status')->in($status)->fi() ->andWhere('t1.type')->eq($storyType) ->andWhere('t1.deleted')->eq(0) @@ -3079,9 +3080,9 @@ class storyModel extends model { $storyQuery = str_replace($allBranch, '1', $storyQuery); } - elseif($branch !== 'all' and $queryProductID != 'all') + elseif($branch !== 'all' and $branch !== '' and strpos($storyQuery, '`branch` =') === false and $queryProductID != 'all') { - if($branch and strpos($storyQuery, '`branch` =') === false) $storyQuery .= " AND `branch` in($branch)"; + $storyQuery .= " AND `branch` in($branch)"; } $storyQuery = preg_replace("/`plan` +LIKE +'%([0-9]+)%'/i", "CONCAT(',', `plan`, ',') LIKE '%,$1,%'", $storyQuery); @@ -3189,15 +3190,22 @@ class storyModel extends model if(defined('TUTORIAL')) return $this->loadModel('tutorial')->getExecutionStories(); if(!$executionID) return array(); - $execution = $this->dao->findById($executionID)->from(TABLE_PROJECT)->fetch(); + $executions = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->in($executionID)->fetchAll('id'); + $hasProject = false; + $hasExecution = false; + foreach($executions as $execution) + { + if($execution->type == 'project') $hasProject = true; + if($execution->type != 'project') $hasExecution = true; + } $orderBy = str_replace('branch_', 't2.branch_', $orderBy); $type = strtolower($type); + $products = $this->loadModel('product')->getProducts($executionID); if($type == 'bysearch') { $queryID = (int)$param; - $products = $this->loadModel('product')->getProducts($executionID); if($this->session->executionStoryQuery == false) $this->session->set('executionStoryQuery', ' 1 = 1'); if($queryID) @@ -3228,7 +3236,6 @@ class storyModel extends model } $storyQuery = preg_replace('/`(\w+)`/', 't2.`$1`', $storyQuery); - $products = $this->loadModel('product')->getProducts($executionID); if($products) $productID = key($products); $review = $this->getRevertStoryIDList($productID); @@ -3250,7 +3257,7 @@ class storyModel extends model ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t2.product = t3.id') ->beginIF(strpos($storyQuery, 'result') !== false)->leftJoin(TABLE_STORYREVIEW)->alias('t4')->on('t2.id = t4.story and t2.version = t4.version')->fi() ->where($storyQuery) - ->andWhere('t1.project')->eq((int)$executionID) + ->andWhere('t1.project')->in($executionID) ->andWhere('t2.deleted')->eq(0) ->andWhere('t3.deleted')->eq(0) ->andWhere('t2.type')->eq($storyType) @@ -3289,18 +3296,18 @@ class storyModel extends model $stories = $this->dao->select("distinct t1.*, t2.*, IF(t2.`pri` = 0, {$this->config->maxPriValue}, t2.`pri`) as priOrder, t3.type as productType, t2.version as version")->from(TABLE_PROJECTSTORY)->alias('t1') ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t2.product = t3.id') - ->where('t1.project')->eq((int)$executionID) + ->where('t1.project')->in($executionID) ->andWhere('t2.type')->eq($storyType) ->beginIF($excludeStories)->andWhere('t2.id')->notIN($excludeStories)->fi() - ->beginIF($execution->type == 'project') - ->beginIF(!empty($productID))->andWhere('t1.product')->eq($productID) + ->beginIF($hasProject) + ->beginIF(!empty($productID))->andWhere('t1.product')->eq($productID)->fi() ->beginIF($type == 'bybranch' and $branchParam !== '')->andWhere('t2.branch')->in("0,$branchParam")->fi() ->beginIF(strpos('draft|reviewing|changing|closed', $type) !== false)->andWhere('t2.status')->eq($type)->fi() ->beginIF($type == 'unclosed')->andWhere('t2.status')->in(array_keys($unclosedStatus))->fi() ->beginIF($type == 'linkedexecution')->andWhere('t2.id')->in($storyIdList)->fi() ->beginIF($type == 'unlinkedexecution')->andWhere('t2.id')->notIn($storyIdList)->fi() ->fi() - ->beginIF($execution->type != 'project') + ->beginIF($hasExecution) ->beginIF(!empty($productParam))->andWhere('t1.product')->eq($productParam)->fi() ->beginIF($this->session->executionStoryBrowseType and strpos('changing|', $this->session->executionStoryBrowseType) !== false)->andWhere('t2.status')->in(array_keys($unclosedStatus))->fi() ->fi() @@ -4352,7 +4359,7 @@ class storyModel extends model $title = $story->status == 'changing' ? $this->lang->story->recallChange : $this->lang->story->recall; $menu .= $this->buildMenu('story', 'recall', $params . "&from=view&confirm=no&storyType=$story->type", $story, $type, 'undo', 'hiddenwin', 'showinonlybody', false, '', $title); - $menu .= $this->buildMenu('story', 'review', $params . "&from=product&storyType=$story->type", $story, $type, 'search', '', 'showinonlybody'); + $menu .= $this->buildMenu('story', 'review', $params . "&from={$this->app->tab}&storyType=$story->type", $story, $type, 'search', '', 'showinonlybody'); $executionID = empty($execution) ? 0 : $execution->id; if(!isonlybody()) $menu .= $this->buildMenu('story', 'batchCreate', "productID=$story->product&branch=$story->branch&moduleID=$story->module&$params&executionID=$executionID&plan=0&storyType=story", $story, $type, 'split', '', 'divideStory', true, "data-toggle='modal' data-type='iframe' data-width='95%'", $this->lang->story->subdivide); @@ -4435,7 +4442,7 @@ class storyModel extends model { $reviewDisabled = in_array($this->app->user->account, $story->notReview) and ($story->status == 'draft' or $story->status == 'changing') ? '' : 'disabled'; $story->from = 'execution'; - $menu .= common::printIcon('story', 'review', "story={$story->id}&from=story", $story, 'list', 'search', '', $reviewDisabled, false, "data-group=execution"); + $menu .= common::printIcon('story', 'review', "story={$story->id}&from=execution", $story, 'list', 'search', '', $reviewDisabled, false, "data-group=execution"); } } @@ -5776,8 +5783,7 @@ class storyModel extends model if(isset($story->finalResult)) { - $isChanged = $story->changedBy ? true : false; - if($story->finalResult == 'reject') $this->action->create('story', $story->id, 'ReviewRejected', '', $isChanged ? 'changing' : 'draft'); + if($story->finalResult == 'reject') $this->action->create('story', $story->id, 'ReviewRejected'); if($story->finalResult == 'pass') $this->action->create('story', $story->id, 'ReviewPassed'); if($story->finalResult == 'clarify') $this->action->create('story', $story->id, 'ReviewClarified'); if($story->finalResult == 'revert') $this->action->create('story', $story->id, 'ReviewReverted'); @@ -5849,7 +5855,7 @@ class storyModel extends model */ public function getRelatedObjects($object, $pairs = '') { - $storys = $this->loadModel('port')->getQueryDatas('story'); + $storys = $this->loadModel('transfer')->getQueryDatas('story'); /* Get related objects id lists. */ $relatedObjectIdList = array(); @@ -6068,13 +6074,13 @@ class storyModel extends model $lastRecord = $this->dao->select('action,extra')->from(TABLE_ACTION) ->where('objectType')->eq('story') ->andWhere('objectID')->eq($storyID) + ->andWhere('action')->in('closed,reviewrejected') ->orderBy('id_desc') ->fetch(); $lastAction = $lastRecord->action; - $preStatus = $lastRecord->extra; - if(strpos($preStatus, '|') !== false) $preStatus = substr($preStatus, strpos($preStatus, '|') + 1); - if(strpos('closed,reviewrejected', $lastAction) !== false) $status = $preStatus; + if($lastAction == 'reviewrejected') $status = 'draft'; + if($lastAction == 'closed') $status = strpos($lastRecord->extra, '|') !== false ? substr($lastRecord->extra, strpos($lastRecord->extra, '|') + 1) : 'active'; return $status; } diff --git a/module/story/view/batchclose.html.php b/module/story/view/batchclose.html.php index 839f957a3a..be938128d6 100755 --- a/module/story/view/batchclose.html.php +++ b/module/story/view/batchclose.html.php @@ -28,7 +28,8 @@ - + + $story):?> @@ -43,15 +44,15 @@ -
story->stageList[$story->stage];?> id&story=$story->id"); + $unlinkURL = $this->createLink($module, 'unlinkStory', "buildID=$build->id&story=$story->id"); echo html::a($unlinkURL, '', 'hiddenwin', "class='btn' title='{$lang->build->unlinkStory}'"); } ?> @@ -163,13 +165,13 @@ tbody tr td:first-child input {display: none;}
' id='bugs'> - +
id, \"bug\")", ' ' . $lang->build->linkBug, '', "class='btn btn-primary'");?>
-
id");?>" id='linkedBugsForm'> + id");?>" id='linkedBugsForm'> - + id}&type=bug&link=$link¶m=$param&orderBy=%s";?> @@ -214,9 +216,9 @@ tbody tr td:first-child input {display: none;} + execution):?> + + + + + + + + + + @@ -369,6 +388,7 @@ tbody tr td:first-child input {display: none;} +id)?> diff --git a/module/common/lang/menu.php b/module/common/lang/menu.php index adffa35d69..73dd7ce3dd 100644 --- a/module/common/lang/menu.php +++ b/module/common/lang/menu.php @@ -212,7 +212,7 @@ $lang->scrum->menu->projectplan = array('link' => "{$lang->productplan->shortCom $lang->scrum->menu->doc = array('link' => "{$lang->doc->common}|doc|tableContents|type=project&objectID=%s", 'subModule' => 'doc'); $lang->scrum->menu->qa = array('link' => "{$lang->qa->common}|project|bug|projectID=%s", 'subModule' => 'testcase,testtask,bug,testreport', 'alias' => 'bug,testtask,testcase,testreport', 'exclude' => 'execution-create,execution-batchedit'); $lang->scrum->menu->devops = array('link' => "{$lang->repo->common}|repo|browse|repoID=0&branchID=&objectID=%s", 'subModule' => 'repo'); -$lang->scrum->menu->build = array('link' => "{$lang->build->common}|project|build|project=%s"); +$lang->scrum->menu->build = array('link' => "{$lang->build->common}|project|build|project=%s", 'subModule' => 'projectbuild'); $lang->scrum->menu->release = array('link' => "{$lang->release->common}|projectrelease|browse|project=%s", 'subModule' => 'projectrelease'); $lang->scrum->menu->dynamic = array('link' => "$lang->dynamic|project|dynamic|project=%s"); $lang->scrum->menu->settings = array('link' => "$lang->settings|project|view|project=%s", 'subModule' => 'tree,stakeholder', 'alias' => 'edit,manageproducts,group,managemembers,manageview,managepriv,whitelist,addwhitelist,team,managerepo'); diff --git a/module/cron/control.php b/module/cron/control.php index 7b863918b3..4283809823 100644 --- a/module/cron/control.php +++ b/module/cron/control.php @@ -186,7 +186,7 @@ class cron extends control /* Skip cron that status is running and run time is less than max. */ if($cronInfo->status == 'running' and (time() - strtotime($cronInfo->lastTime)) < $this->config->cron->maxRunTime) continue; /* Skip cron that last time is more than this cron time. */ - if ('cli' === PHP_SAPI) + if('cli' === PHP_SAPI) { if($cronInfo->lastTime >= $cron['time']->format(DT_DATETIME1)) continue; } @@ -197,10 +197,7 @@ class cron extends control if($now > $cron['time']) { - if (!$this->cron->changeStatusRunning($id, $cronInfo->lastTime)) - { - continue; - } + if(!$this->cron->changeStatusRunning($id, $cronInfo->lastTime)) continue; $parsedCrons[$id]['time'] = $cron['cron']->getNextRunDate(); /* Execution command. */ @@ -259,5 +256,4 @@ class cron extends control /* Revert cron status to stop. */ $this->cron->markCronStatus('stop', $configID); } - } diff --git a/module/cron/model.php b/module/cron/model.php index ef1ec7a580..1b1db8be3d 100644 --- a/module/cron/model.php +++ b/module/cron/model.php @@ -98,21 +98,16 @@ class cronModel extends model * Change cron status to running * * @param int $cronID - * @param string $lastTime * @access public - * @return bool|int + * @return bool */ - public function changeStatusRunning($cronID, $lastTime) + public function changeStatusRunning($cronID) { $data = new stdclass(); - $data->status = 'running'; + $data->status = 'running'; $data->lastTime = date(DT_DATETIME1); - $rows = $this->dao->update(TABLE_CRON)->data($data) - ->where('id')->eq($cronID) - ->andWhere('status')->ne('running') - ->andWhere('lastTime')->eq($lastTime) - ->exec(); - return dao::isError() ? false : $rows; + $this->dao->update(TABLE_CRON)->data($data)->where('id')->eq($cronID)->exec(); + return !dao::isError(); } /** diff --git a/module/custom/config.php b/module/custom/config.php index 9998ce947e..59db854527 100644 --- a/module/custom/config.php +++ b/module/custom/config.php @@ -22,8 +22,8 @@ $config->custom->requiredModules[35] = 'execution'; $config->custom->requiredModules[40] = 'task'; $config->custom->requiredModules[45] = 'build'; -$config->custom->allFeatures = array('program', 'productRR', 'productUR', 'productLine', 'projectScrum', 'projectWaterfall', 'projectKanban', 'execution', 'vision'); -$config->custom->features = array('waterfall', 'productUR'); +$config->custom->allFeatures = array('program', 'productRR', 'productUR', 'productLine', 'projectScrum', 'projectWaterfall', 'projectKanban', 'execution', 'vision'); +$config->custom->dataFeatures = array('waterfall', 'productUR'); $config->custom->requiredModules[50] = 'bug'; $config->custom->requiredModules[55] = 'testcase'; @@ -42,8 +42,8 @@ $config->custom->fieldList['project']['create'] = 'budget,PM,desc'; $config->custom->fieldList['project']['edit'] = 'budget,PM,desc'; $config->custom->fieldList['product']['create'] = 'PO,QD,RD,type,desc'; $config->custom->fieldList['product']['edit'] = 'PO,QD,RD,type,desc,status'; -$config->custom->fieldList['story']['create'] = 'module,plan,source,pri,estimate,keywords'; -$config->custom->fieldList['story']['change'] = 'comment'; +$config->custom->fieldList['story']['create'] = 'module,plan,source,pri,estimate,keywords,spec'; +$config->custom->fieldList['story']['change'] = 'comment,spec'; $config->custom->fieldList['story']['close'] = 'comment'; $config->custom->fieldList['story']['review'] = 'reviewedDate,comment'; $config->custom->fieldList['productplan'] = 'begin,end,desc'; diff --git a/module/custom/control.php b/module/custom/control.php index aa2d7a161c..a100b55f83 100644 --- a/module/custom/control.php +++ b/module/custom/control.php @@ -644,8 +644,8 @@ class custom extends control return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'top')); } - $disabledFeatures = array('program', 'productLine'); - foreach($this->config->custom->features as $feature) + $disabledFeatures = array('program', 'productLine', 'scrum' => array('scrumMeasrecord')); + foreach($this->config->custom->dataFeatures as $feature) { $function = 'has' . ucfirst($feature) . 'Data'; if(!$this->custom->$function()) @@ -665,18 +665,24 @@ class custom extends control } } - $enabledScrumFeatures = array(); - $disabledScrumFeatures = array(); - foreach($this->config->custom->scrumFeatures as $scrumFeature) + if($this->config->edition == 'max') { - if(in_array('scrum' . ucfirst($scrumFeature), $disabledFeatures['scrum'])) + $enabledScrumFeatures = array(); + $disabledScrumFeatures = array(); + foreach($this->config->custom->scrumFeatures as $scrumFeature) { - $disabledScrumFeatures[] = $this->lang->custom->scrum->features[$scrumFeature]; - } - else - { - $enabledScrumFeatures[] = $this->lang->custom->scrum->features[$scrumFeature]; + if(in_array('scrum' . ucfirst($scrumFeature), $disabledFeatures['scrum'])) + { + $disabledScrumFeatures[] = $this->lang->custom->scrum->features[$scrumFeature]; + } + else + { + $enabledScrumFeatures[] = $this->lang->custom->scrum->features[$scrumFeature]; + } } + + $this->view->enabledScrumFeatures = implode($this->lang->comma, $enabledScrumFeatures); + $this->view->disabledScrumFeatures = implode($this->lang->comma, $disabledScrumFeatures); } $this->app->loadLang('upgrade'); @@ -688,8 +694,6 @@ class custom extends control $this->view->programs = $this->loadModel('program')->getTopPairs('', 'noclosed', true); $this->view->programID = isset($this->config->global->defaultProgram) ? $this->config->global->defaultProgram : 0; $this->view->disabledFeatures = $disabledFeatures; - $this->view->enabledScrumFeatures = implode($this->lang->comma, $enabledScrumFeatures); - $this->view->disabledScrumFeatures = implode($this->lang->comma, $disabledScrumFeatures); $this->display(); } diff --git a/module/custom/model.php b/module/custom/model.php index af0bea4f8a..6e124ef93b 100644 --- a/module/custom/model.php +++ b/module/custom/model.php @@ -897,7 +897,7 @@ class customModel extends model $disabledFeatures = ''; if($mode == 'light') { - foreach($this->config->custom->features as $feature) + foreach($this->config->custom->dataFeatures as $feature) { $function = 'has' . ucfirst($feature) . 'Data'; if(!$this->$function()) $disabledFeatures .= "$feature,"; diff --git a/module/custom/view/mode.html.php b/module/custom/view/mode.html.php index 0a80e973fe..1c215735c0 100644 --- a/module/custom/view/mode.html.php +++ b/module/custom/view/mode.html.php @@ -45,7 +45,7 @@ custom->allFeatures as $feature):?> - + config->edition == 'max' && $feature == 'scrumDetail'):?> diff --git a/module/design/control.php b/module/design/control.php old mode 100644 new mode 100755 index 79981d19a8..9a78f85dc4 --- a/module/design/control.php +++ b/module/design/control.php @@ -39,8 +39,12 @@ class design extends control */ public function commonAction($projectID = 0, $productID = 0, $designID = 0) { - $products = $this->product->getProductPairsByProject($projectID); - $productID = $this->product->saveState($productID, $products); + $products = $this->product->getProductPairsByProject($projectID); + $products[0] = $this->lang->product->all; + + ksort($products); + + $productID = $this->product->saveState($productID, $products); $this->lang->modulePageNav = $this->design->setMenu($projectID, $products, $productID); $this->project->setMenu($projectID); @@ -158,12 +162,16 @@ class design extends control return $this->send($response); } + $products = $this->product->getProductPairsByProject($projectID); + $productIdList = $productID ? $productID : array_keys($products); + $this->view->title = $this->lang->design->common . $this->lang->colon . $this->lang->design->create; $this->view->position[] = $this->lang->design->create; $this->view->users = $this->loadModel('user')->getPairs('noclosed'); - $this->view->stories = $this->loadModel('story')->getProductStoryPairs($productID, 'all', 0, 'active'); + $this->view->stories = $this->loadModel('story')->getProductStoryPairs($productIdList, 'all', 0, 'active', 'id_desc', 0, 'full', 'story', false); $this->view->productID = $productID; + $this->view->projectID = $projectID; $this->view->type = $type; $this->view->project = $this->loadModel('project')->getByID($projectID); @@ -201,10 +209,13 @@ class design extends control return $this->send($response); } + $products = $this->product->getProductPairsByProject($projectID); + $productIdList = $productID ? $productID : array_keys($products); + $this->view->title = $this->lang->design->common . $this->lang->colon . $this->lang->design->batchCreate; $this->view->position[] = $this->lang->design->batchCreate; - $this->view->stories = $this->loadModel('story')->getProductStoryPairs($productID); + $this->view->stories = $this->loadModel('story')->getProductStoryPairs($productIdList); $this->view->users = $this->loadModel('user')->getPairs('noclosed'); $this->view->type = $type; @@ -226,11 +237,14 @@ class design extends control $this->session->set('revisionList', $this->app->getURI(true)); $this->session->set('storyList', $this->app->getURI(true), 'product'); + $products = $this->product->getProductPairsByProject($design->project); + $productIdList = $design->product ? $design->product : array_keys($products); + $this->view->title = $this->lang->design->common . $this->lang->colon . $this->lang->design->view; $this->view->position[] = $this->lang->design->view; $this->view->design = $design; - $this->view->stories = $this->loadModel('story')->getProductStoryPairs($design->product); + $this->view->stories = $this->loadModel('story')->getProductStoryPairs($productIdList); $this->view->users = $this->loadModel('user')->getPairs('noletter'); $this->view->actions = $this->loadModel('action')->getList('design', $design->id); $this->view->repos = $this->loadModel('repo')->getRepoPairs('project', $design->project); @@ -275,12 +289,15 @@ class design extends control return $this->send($response); } + $products = $this->product->getProductPairsByProject($design->project); + $productIdList = $design->product ? $design->product : array_keys($products); + $this->view->title = $this->lang->design->common . $this->lang->colon . $this->lang->design->edit; $this->view->position[] = $this->lang->design->edit; $this->view->design = $design; $this->view->project = $this->loadModel('project')->getByID($design->project); - $this->view->stories = $this->loadModel('story')->getProductStoryPairs($design->product); + $this->view->stories = $this->loadModel('story')->getProductStoryPairs($productIdList); $this->view->users = $this->loadModel('user')->getPairs('noclosed'); $this->display(); @@ -450,6 +467,26 @@ class design extends control $this->display(); } + /** + * Ajax get stories by productID and projectID. + * + * @param int $productID + * @param int $projectID + * @param string $status + * @param string $hasParent + * @access public + * @return void + */ + public function ajaxGetProductStories($productID, $projectID, $status = 'all', $hasParent = 'true') + { + $products = $this->product->getProductPairsByProject($projectID); + $productIdList = $productID ? $productID : array_keys($products); + + $stories = $this->loadModel('story')->getProductStoryPairs($productIdList, 'all', 0, $status, 'id_desc', 0, 'full', 'story', $hasParent); + + return print(html::select('story', $stories, '', "class='form-control'")); + } + /** * Delete a design. * diff --git a/module/design/js/common.js b/module/design/js/common.js old mode 100644 new mode 100755 index 7fbbb1f978..b49bdce6cc --- a/module/design/js/common.js +++ b/module/design/js/common.js @@ -1,12 +1,12 @@ $(document).on('click', '.ajaxPager', function() -{ +{ $('#logBox').load($(this).attr('href')); return false; }) $('#product').change(function() { productID = $(this).val(); - var link = createLink('story', 'ajaxGetProductStories', 'productID=' + productID + '&branch=all&moduleID=0&storyID=0&onlyOption=false&status=active'); + var link = createLink('design', 'ajaxGetProductStories', 'productID=' + productID + '&projectID=' + projectID + '&status=active&hasParent=false'); $.post(link, function(data) { $('#story').replaceWith(data); diff --git a/module/design/model.php b/module/design/model.php old mode 100644 new mode 100755 index 811cc87f4d..5d12207bf8 --- a/module/design/model.php +++ b/module/design/model.php @@ -240,11 +240,12 @@ class designModel extends model */ public function getByID($designID = 0) { + $this->app->loadLang('product'); $design = $this->dao->select('*')->from(TABLE_DESIGN)->where('id')->eq($designID)->fetch(); if(!$design) return false; $design->files = $this->loadModel('file')->getByObject('design', $designID); - $design->productName = $this->dao->findByID($design->product)->from(TABLE_PRODUCT)->fetch('name'); + $design->productName = $design->product ? $this->dao->findByID($design->product)->from(TABLE_PRODUCT)->fetch('name') : $this->lang->product->all; $design->commit = ''; @@ -317,7 +318,7 @@ class designModel extends model ->where('deleted')->eq(0) ->beginIF($projectID)->andWhere('project')->eq($projectID)->fi() ->beginIF($type != 'all')->andWhere('type')->in($type)->fi() - ->andWhere('product')->eq($productID) + ->beginIF($productID)->andWhere('product')->eq($productID)->fi() ->orderBy($orderBy) ->page($pager) ->fetchAll('id'); @@ -376,11 +377,11 @@ class designModel extends model $designQuery = $this->session->designQuery; - $designs = $this->dao->select('*')->from(TABLE_DESIGN) + $designs = $this->dao->select('*')->from(TABLE_DESIGN) ->where($designQuery) ->andWhere('deleted')->eq('0') ->andWhere('project')->eq($projectID) - ->andWhere('product')->eq($productID) + ->beginIF($productID)->andWhere('product')->eq($productID)->fi() ->orderBy($orderBy) ->page($pager) ->fetchAll('id'); @@ -426,7 +427,16 @@ class designModel extends model if(empty($products) || !$productID) return ''; - setCookie("lastProduct", $productID, $this->config->cookieLife, $this->config->webRoot, '', false, true); + if($productID) + { + $currentProduct = $this->loadModel('product')->getById($productID); + setCookie("lastProduct", $productID, $this->config->cookieLife, $this->config->webRoot, '', false, true); + } + else + { + $currentProduct = new stdclass(); + $currentProduct->name = $this->lang->product->all; + } $currentProduct = $this->loadModel('product')->getById($productID); if(!empty($currentProduct->shadow)) return ''; diff --git a/module/design/view/ajaxgetdropmenu.html.php b/module/design/view/ajaxgetdropmenu.html.php old mode 100644 new mode 100755 index 19ebf15310..3b91968cba --- a/module/design/view/ajaxgetdropmenu.html.php +++ b/module/design/view/ajaxgetdropmenu.html.php @@ -43,6 +43,9 @@ foreach($products as $product)
createLink('design', 'browse', "projectID=$projectID"), $lang->product->all, '', "class=$selected data-app='$tab'"); + if(!empty($myProductsHtml)) { echo "
{$lang->product->mine}
"; diff --git a/module/design/view/browse.html.php b/module/design/view/browse.html.php index 6405b28ba9..495122acb6 100644 --- a/module/design/view/browse.html.php +++ b/module/design/view/browse.html.php @@ -34,6 +34,7 @@
+ @@ -46,6 +47,7 @@ + diff --git a/module/design/view/create.html.php b/module/design/view/create.html.php old mode 100644 new mode 100755 index 5658c47786..f79116a6b9 --- a/module/design/view/create.html.php +++ b/module/design/view/create.html.php @@ -13,6 +13,7 @@ +
diff --git a/module/design/view/edit.html.php b/module/design/view/edit.html.php old mode 100644 new mode 100755 index bf0385d656..6748509152 --- a/module/design/view/edit.html.php +++ b/module/design/view/edit.html.php @@ -13,6 +13,7 @@ type);?> +project);?>
diff --git a/module/design/view/view.html.php b/module/design/view/view.html.php old mode 100644 new mode 100755 diff --git a/module/dev/model.php b/module/dev/model.php index 42a52c5914..2454f5bd07 100644 --- a/module/dev/model.php +++ b/module/dev/model.php @@ -154,7 +154,7 @@ class devModel extends model foreach($methods as $method) { if($method->class == 'baseControl' or $method->class == 'control' or $method->name == '__construct') continue; - $api = array('name' => $method->name, 'post' => false, 'param' => array()); + $api = array('name' => $method->name, 'post' => false, 'param' => array(), 'desc' => ''); $methodReflect = new ReflectionMethod($module, $method->name); foreach($methodReflect->getParameters() as $key => $param) { diff --git a/module/doc/css/common.css b/module/doc/css/common.css index a9b746c106..ac104128cb 100644 --- a/module/doc/css/common.css +++ b/module/doc/css/common.css @@ -102,5 +102,5 @@ ol, ul {margin-bottom: 0} .fullscreen-close {top: 8px !important;} -#subHeader .list-group>a.selected {color: #e9f2fb !important; background-color: #0c64eb !important;} +#subHeader .list-group>a.selected {color: #e9f2fb !important;} #pageNav #dropMenu .table-col .list-group .icon-move {display:block; float: left; font-size: 12px; padding: 3px 4px 3px 1px;} diff --git a/module/doc/js/objectlibs.js b/module/doc/js/objectlibs.js index a661bf9b5d..7f8d8f5f36 100644 --- a/module/doc/js/objectlibs.js +++ b/module/doc/js/objectlibs.js @@ -181,6 +181,12 @@ $(function() $('#content [data-ride="tree"]').tree(); $('#outline li.has-list').addClass('open in'); $('#outline li.has-list>i+ul').prev('i').remove(); + if($('#markdownContent').val()) + { + var simplemde = new SimpleMDE({element: $("#markdownContent")[0],toolbar:false, status: false}); + simplemde.value(String($('#markdownContent').val())); + simplemde.togglePreview(); + } }); }); diff --git a/module/doc/model.php b/module/doc/model.php index 48d24222c1..e6ad6802d3 100644 --- a/module/doc/model.php +++ b/module/doc/model.php @@ -654,7 +654,7 @@ class docModel extends model if($version == $doc->version and ((empty($docContent->files) and $docFiles) or ($docContent->files and count(explode(',', trim($docContent->files, ','))) != count($docFiles)))) { unset($docContent->id); - $doc->version += 1; + $doc->version += 1; $docContent->version = $doc->version; $docContent->files = join(',', array_keys($docFiles)); $this->dao->insert(TABLE_DOCCONTENT)->data($docContent)->exec(); @@ -930,13 +930,6 @@ class docModel extends model { $this->loadModel('product'); - /* Fixed search modal for doc view. */ - if($this->app->getMethodName() == 'objectlibs') - { - $queryName = $type . 'Doc'; - $type = 'objectLibs'; - } - if($this->app->rawMethod == 'contribute') { $this->config->doc->search['module'] = 'contributeDoc'; @@ -960,7 +953,6 @@ class docModel extends model } else { - if(isset($queryName)) $this->config->doc->search['module'] = $queryName; $products = $this->product->getPairs('nocode', $this->session->project); $this->config->doc->search['params']['execution']['values'] = array('' => '') + $this->loadModel('execution')->getPairs($this->session->project, 'all', 'noclosed') + array('all' => $this->lang->doc->allExecutions); $this->config->doc->search['params']['lib']['values'] = array('' => '', $libID => ($libID ? $libs[$libID] : 0), 'all' => $this->lang->doclib->all); diff --git a/module/doc/view/content.html.php b/module/doc/view/content.html.php index fa12f06ff7..4a8670fe38 100644 --- a/module/doc/view/content.html.php +++ b/module/doc/view/content.html.php @@ -103,7 +103,7 @@ } elseif($doc->contentType == 'markdown') { - echo ""; + echo ""; } else { diff --git a/module/execution/control.php b/module/execution/control.php index 63e8db7704..6d0e3736eb 100644 --- a/module/execution/control.php +++ b/module/execution/control.php @@ -739,6 +739,7 @@ class execution extends control /* Load these models. */ $this->loadModel('story'); $this->loadModel('user'); + $this->loadModel('product'); $this->loadModel('datatable'); $this->app->loadLang('datatable'); $this->app->loadLang('testcase'); @@ -1240,7 +1241,6 @@ class execution extends control */ public function testreport($executionID = 0, $objectType = 'execution', $extra = '', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) { - if($this->app->tab == 'project') $this->loadModel('project')->setMenu($this->session->project); echo $this->fetch('testreport', 'browse', "objectID=$executionID&objectType=$objectType&extra=$extra&orderBy=$orderBy&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID"); } @@ -1272,15 +1272,16 @@ class execution extends control $queryID = ($type == 'bysearch') ? (int)$param : 0; $actionURL = $this->createLink('execution', 'build', "executionID=$executionID&type=bysearch&queryID=myQueryID"); + $this->loadModel('build'); $product = $param ? $this->loadModel('product')->getById($param) : ''; if($product and $product->type != 'normal') { - $this->loadModel('build'); $this->loadModel('branch'); $branches = array(BRANCH_MAIN => $this->lang->branch->main) + $this->branch->getPairs($product->id, '', $executionID); $this->config->build->search['fields']['branch'] = sprintf($this->lang->build->branchName, $this->lang->product->branchName[$product->type]); $this->config->build->search['params']['branch'] = array('operator' => '=', 'control' => 'select', 'values' => $branches); } + if(!$execution->hasProduct) unset($this->config->build->search['fields']['product']); $this->project->buildProjectBuildSearchForm($products, $queryID, $actionURL, 'execution'); /* Get builds. */ @@ -1359,7 +1360,7 @@ class execution extends control $productTasks = array(); - $tasks = $this->testtask->getExecutionTasks($executionID, $orderBy, $pager); + $tasks = $this->testtask->getExecutionTasks($executionID, 'execution', $orderBy, $pager); foreach($tasks as $key => $task) $productTasks[$task->product][] = $task; $this->view->title = $this->executions[$executionID] . $this->lang->colon . $this->lang->testtask->common; @@ -1712,8 +1713,30 @@ class execution extends control $this->view->linkedBranches = $linkedBranches; } + if(!empty($project) and !$project->division) + { + $products = $this->loadModel('product')->getProducts($projectID); + $branches = $this->project->getBranchesByProject($projectID); + $plans = $this->loadModel('productplan')->getGroupByProduct(array_keys($products), 'skipParent|unexpired'); + + $linkedBranches = array(); + foreach($products as $productID => $product) + { + foreach($branches[$productID] as $branchID => $branch) + { + $linkedBranches[$productID][$branchID] = $branchID; + $productPlans[$productID][$branchID] = isset($plans[$productID][$branchID]) ? $plans[$productID][$branchID] : array(); + } + } + + $this->view->branches = $branches; + $this->view->linkedBranches = $linkedBranches; + } + if(!empty($_POST)) { + if(isset($_POST['attribute']) and in_array($_POST['attribute'], array('request', 'design', 'review'))) unset($_POST['plans']); + $executionID = $this->execution->create($copyExecutionID); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); @@ -1803,7 +1826,7 @@ class execution extends control $this->view->code = $code; $this->view->team = $team; $this->view->teams = array(0 => '') + $this->execution->getCanCopyObjects((int)$projectID); - $this->view->allProjects = array(0 => '') + $this->project->getPairsByModel('all', 0, 'noclosed|nomultiple'); + $this->view->allProjects = array(0 => '') + $this->project->getPairsByModel('all', 0, 'noclosed'); $this->view->copyProjects = $copyProjects; $this->view->copyExecutions = array('' => '') + $this->execution->getList($copyProjectID); $this->view->executionID = $executionID; @@ -1825,6 +1848,7 @@ class execution extends control $this->view->from = $this->app->tab; $this->view->isStage = (isset($project->model) and $project->model == 'waterfall') ? true : false; $this->view->project = $project; + $this->view->division = !empty($project) ? $project->division : 1; $this->display(); } diff --git a/module/execution/js/common.js b/module/execution/js/common.js index c49d678d02..19d68b6372 100644 --- a/module/execution/js/common.js +++ b/module/execution/js/common.js @@ -124,13 +124,15 @@ function loadBranches(product) { if($('#productsBox .input-group:last select:first').val() != 0) { - var length = $('#productsBox .input-group').size(); - $('#productsBox .row').append('
' + $('#productsBox .col-sm-4:last').html().replace('required', '') + '
'); - if($('#productsBox .input-group:last select').size() >= 2) $('#productsBox .input-group:last select:last').remove(); - $('#productsBox .input-group:last .chosen-container').remove(); - $('#productsBox .input-group:last select:first').attr('name', 'products[' + length + ']').attr('id', 'products' + length); - $('#productsBox .input-group:last .chosen').chosen(); - + if(model !== 'waterfall') + { + var length = $('#productsBox .input-group').size(); + $('#productsBox .row').append('
' + $('#productsBox .col-sm-4:last').html().replace('required', '') + '
'); + if($('#productsBox .input-group:last select').size() >= 2) $('#productsBox .input-group:last select:last').remove(); + $('#productsBox .input-group:last .chosen-container').remove(); + $('#productsBox .input-group:last select:first').attr('name', 'products[' + length + ']').attr('id', 'products' + length); + $('#productsBox .input-group:last .chosen').chosen(); + } adjustProductBoxMargin(); } diff --git a/module/execution/js/create.js b/module/execution/js/create.js index ef36d8ce1b..52ae3473cd 100644 --- a/module/execution/js/create.js +++ b/module/execution/js/create.js @@ -74,6 +74,24 @@ $(function() }); }) + if(isStage) + { + $('#attribute').change(function() + { + var attribute = $(this).val(); + if(attribute == 'request' || attribute == 'design' || attribute == 'review') + { + $('#plansBox').closest('tr').addClass('hide'); + } + else + { + $('#plansBox').closest('tr').removeClass('hide'); + } + }) + + $('#attribute').change(); + } + if(copyExecutionID != 0 || projectID != 0) $('#teams').change(); var acl = $("[name^='acl']:checked").val(); diff --git a/module/execution/js/kanban.js b/module/execution/js/kanban.js index 35ba15e23e..9332709f9a 100644 --- a/module/execution/js/kanban.js +++ b/module/execution/js/kanban.js @@ -1347,7 +1347,7 @@ function createTaskMenu(options) if(priv.canPauseTask && task.$col.type == 'developing') items.push({label: taskLang.pause, icon: 'pause', url: createLink('task', 'pause', 'taskID=' + task.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}}); if(priv.canRecordEstimateTask) items.push({label: executionLang.effort, icon: 'time', url: createLink('task', 'recordEstimate', 'taskID=' + task.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}}); if(priv.canActivateTask && (task.$col.type == 'developed' || task.$col.type == 'canceled' || task.$col.type == 'closed')) items.push({label: executionLang.activate, icon: 'magic', url: createLink('task', 'activate', 'taskID=' + task.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}}); - if(priv.canCreateTask && !task.mode) items.push({label: taskLang.copy, icon: 'copy', url: createLink('task', 'create', 'executionID=' + executionID + '&storyID=' + '0' + '&moduleID=' + '0' + '&taskID=' + task.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}}); + if(priv.canCreateTask) items.push({label: taskLang.copy, icon: 'copy', url: createLink('task', 'create', 'executionID=' + executionID + '&storyID=' + '0' + '&moduleID=' + '0' + '&taskID=' + task.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}}); if(priv.canCancelTask && (task.$col.type == 'wait' || task.$col.type == 'developing' || task.$col.type == 'pause')) items.push({label: taskLang.cancel, icon: 'cancel', url: createLink('task', 'cancel', 'taskID=' + task.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}}); if(priv.canDeleteTask) items.push({label: taskLang.delete, icon: 'trash', onClick: function(){deleteCard('task', task.id, task.$lane.region)}}); return items; diff --git a/module/execution/lang/de.php b/module/execution/lang/de.php index 8e163e60bf..29c7ef95cb 100644 --- a/module/execution/lang/de.php +++ b/module/execution/lang/de.php @@ -363,7 +363,7 @@ $lang->execution->noExecution = 'Keine Projekte. '; $lang->execution->noExecutions = "No {$lang->execution->common}."; $lang->execution->noPrintData = "No data can be printed."; $lang->execution->noMembers = 'Keine Mitglieder. '; -$lang->execution->workloadTotal = "The cumulative workload ratio should not exceed 100, and the total workload under the current product is: %s"; +$lang->execution->workloadTotal = "The cumulative workload ratio should not exceed 100%s, and the total workload under the current product is: %s"; // $lang->execution->linkProjectStoryTip = "(Link {$lang->SRCommon} comes from {$lang->SRCommon} linked under the execution)"; $lang->execution->linkAllStoryTip = "({$lang->SRCommon} has never been linked under the project, and can be directly linked with {$lang->SRCommon} of the product linked with the sprint/stage)"; $lang->execution->copyTeamTitle = "Choose a {$lang->project->common} or {$lang->execution->common} Team to copy."; diff --git a/module/execution/lang/en.php b/module/execution/lang/en.php index 92f11dde51..5c7d8daf50 100644 --- a/module/execution/lang/en.php +++ b/module/execution/lang/en.php @@ -363,7 +363,7 @@ $lang->execution->noExecution = "No {$lang->executionCommon}. "; $lang->execution->noExecutions = "No {$lang->execution->common}."; $lang->execution->noPrintData = "No data can be printed."; $lang->execution->noMembers = 'No team members yet. '; -$lang->execution->workloadTotal = "The cumulative workload ratio should not exceed 100, and the total workload under the current product is: %s"; +$lang->execution->workloadTotal = "The cumulative workload ratio should not exceed 100%s, and the total workload under the current product is: %s"; // $lang->execution->linkProjectStoryTip = "(Link {$lang->SRCommon} comes from {$lang->SRCommon} linked under the execution)"; $lang->execution->linkAllStoryTip = "({$lang->SRCommon} has never been linked under the execution, and can be directly linked with {$lang->SRCommon} of the product linked with the sprint/stage)"; $lang->execution->copyTeamTitle = "Choose a {$lang->project->common} or {$lang->execution->common} Team to copy."; diff --git a/module/execution/lang/fr.php b/module/execution/lang/fr.php index 764074cd82..3da5b42ffa 100644 --- a/module/execution/lang/fr.php +++ b/module/execution/lang/fr.php @@ -363,7 +363,7 @@ $lang->execution->noExecution = "Aucun {$lang->executionCommon}. "; $lang->execution->noExecutions = "Aucun {$lang->execution->common}."; $lang->execution->noPrintData = "No data can be printed."; $lang->execution->noMembers = "Actuellement il n'y a aucun membre dans l'équipe. On ne va pas aller loin... "; -$lang->execution->workloadTotal = "The cumulative workload ratio should not exceed 100, and the total workload under the current product is: %s"; +$lang->execution->workloadTotal = "The cumulative workload ratio should not exceed 100%s, and the total workload under the current product is: %s"; // $lang->execution->linkProjectStoryTip = "(Link {$lang->SRCommon} comes from {$lang->SRCommon} linked under the execution)"; $lang->execution->linkAllStoryTip = "({$lang->SRCommon} has never been linked under the project, and can be directly linked with {$lang->SRCommon} of the product linked with the sprint/stage)"; $lang->execution->copyTeamTitle = "Choose a {$lang->project->common} or {$lang->execution->common} Team to copy."; diff --git a/module/execution/lang/zh-cn.php b/module/execution/lang/zh-cn.php index e7ff82518e..77f79dff1f 100644 --- a/module/execution/lang/zh-cn.php +++ b/module/execution/lang/zh-cn.php @@ -363,7 +363,7 @@ $lang->execution->noExecution = "暂时没有{$lang->executionCommon} $lang->execution->noExecutions = "暂时没有{$lang->execution->common}。"; $lang->execution->noPrintData = "暂无数据可打印"; $lang->execution->noMembers = '暂时没有团队成员。'; -$lang->execution->workloadTotal = "工作量占比累计不应当超过100, 当前产品下的工作量之和为%s"; +$lang->execution->workloadTotal = "工作量占比累计不应当超过100%s, 当前产品下的工作量之和为%s"; // $lang->execution->linkProjectStoryTip = "(关联{$lang->SRCommon}来源于项目下所关联的{$lang->SRCommon})"; $lang->execution->linkAllStoryTip = "(项目下还未关联{$lang->SRCommon},可直接关联该{$lang->execution->common}所关联产品的{$lang->SRCommon})"; $lang->execution->copyTeamTitle = "选择一个{$lang->project->common}或{$lang->execution->common}团队"; diff --git a/module/execution/model.php b/module/execution/model.php old mode 100644 new mode 100755 index 39cd21d789..3c2ff6c461 --- a/module/execution/model.php +++ b/module/execution/model.php @@ -64,6 +64,9 @@ class executionModel extends model public function setMenu($executionID, $buildID = 0, $extra = '') { $execution = $this->getByID($executionID); + + if($execution->type == 'stage') unset($this->lang->execution->menu->settings['subMenu']->products); + if($execution and $execution->type == 'kanban') { global $lang; @@ -330,6 +333,12 @@ class executionModel extends model $type = 'sprint'; if($project) $type = zget($this->config->execution->modelList, $project->model, 'sprint'); + if($project->model == 'waterfall') + { + $this->checkWorkload('create', $_POST['percent'], $project); + if(dao::isError()) return false; + } + $this->config->execution->create->requiredFields .= ',project'; /* Judge workdays is legitimate. */ @@ -1179,15 +1188,16 @@ class executionModel extends model ->andWhere('t2.type')->eq('stage') ->andWhere('t2.grade')->eq(1) ->andWhere('t2.deleted')->eq(0) - ->andWhere('t2.parent')->eq($oldExecution) + ->andWhere('t2.parent')->eq($oldExecution->id) ->fetch('total'); + if(!$oldPercentTotal) $oldPercentTotal = 0; if($type == 'create') $percentTotal = $percent + $oldPercentTotal; if(!empty($oldExecution) and $oldExecution->grade == 1) $percentTotal = $oldPercentTotal - $oldExecution->percent + $this->post->percent; if($percentTotal > 100) { - dao::$errors['percent'] = sprintf($this->lang->execution->workloadTotal, $oldPercentTotal . '%'); + dao::$errors['percent'] = sprintf($this->lang->execution->workloadTotal, '%', $oldPercentTotal . '%'); return false; } } @@ -1200,7 +1210,7 @@ class executionModel extends model if($childrenTotalPercent > 100) { - dao::$errors['parent'] = sprintf($this->lang->execution->workloadTotal, $childrenTotalPercent . '%'); + dao::$errors['parent'] = sprintf($this->lang->execution->workloadTotal, '%', $childrenTotalPercent . '%'); return false; } } @@ -1656,7 +1666,7 @@ class executionModel extends model ->beginIF($limit)->limit($limit)->fi() ->fetchAll('id'); - if(isset($project->model) and $project->model == 'waterfall' and $project->hasProduct) + if(isset($project->model) and $project->model == 'waterfall' and $project->hasProduct and $project->division) { $executionList = array(); $executionProducts = $this->dao->select('t1.project,t1.product')->from(TABLE_PROJECTPRODUCT)->alias('t1') @@ -4717,6 +4727,7 @@ class executionModel extends model } } } + if(!empty($execution->division)) echo "
"; echo "'; echo ''; echo helper::isZeroDate($execution->begin) ? '' : ''; @@ -4733,21 +4744,10 @@ class executionModel extends model if($execution->type == 'stage') { - if($execution->grade == 1 && $this->loadModel('programplan')->isCreateTask($execution->id)) - { - common::printIcon('programplan', 'create', "program={$execution->parent}&productID=$productID&planID=$execution->id", $execution, 'list', 'split', '', '', '', '', $this->lang->programplan->createSubPlan); - } - else - { - if($execution->grade == 2) - { - echo ""; - } - else - { - echo common::hasPriv('programplan', 'create') ? html::a('javascript:alert("' . $this->lang->programplan->error->createdTask . '");', '', '', 'class="btn"') : ''; - } - } + $isCreateTask = $this->loadModel('programplan')->isCreateTask($execution->id); + $disabled = ($execution->grade == 1 && $isCreateTask) ? '' : ' disabled'; + $title = !$isCreateTask ? $this->lang->programplan->error->createdTask : $this->lang->programplan->createSubPlan; + common::printIcon('programplan', 'create', "program={$execution->parent}&productID=$productID&planID=$execution->id", $execution, 'list', 'split', '', $disabled, '', '', $title); } if($execution->type == 'stage') @@ -4778,7 +4778,11 @@ class executionModel extends model if(!empty($execution->children)) { - foreach($execution->children as $child) $this->printNestedList($child, true, $users, $productID); + foreach($execution->children as $child) + { + $child->division = $execution->division; + $this->printNestedList($child, true, $users, $productID); + } } if(!empty($execution->tasks)) diff --git a/module/execution/view/create.html.php b/module/execution/view/create.html.php index b58c7dc6e8..5885a10933 100644 --- a/module/execution/view/create.html.php +++ b/module/execution/view/create.html.php @@ -45,6 +45,7 @@ execution->cancelCopy);?> execution->copyNoExecution);?> +model) ? $project->model : '');?>
@@ -134,12 +135,14 @@
diff --git a/module/execution/view/edit.html.php b/module/execution/view/edit.html.php index d999c18a2f..0f6507834d 100644 --- a/module/execution/view/edit.html.php +++ b/module/execution/view/edit.html.php @@ -144,7 +144,7 @@ diff --git a/module/execution/view/testtask.html.php b/module/execution/view/testtask.html.php index aa1d7b993a..3c6ff064e9 100644 --- a/module/execution/view/testtask.html.php +++ b/module/execution/view/testtask.html.php @@ -115,10 +115,7 @@ $doneCount = 0; { common::printIcon('testtask', 'cases', "taskID=$task->id", $task, 'list', 'sitemap'); common::printIcon('testtask', 'linkCase', "taskID=$task->id", $task, 'list', 'link'); - if(common::hasPriv('execution', 'testreport')) - { - echo html::a($this->createLink('execution', 'testreport', "executionID=$executionID&objctType=execution&extra=$task->id"), '', '', 'class="btn " title="' . $lang->testreport->browse . '" data-app="execution"'); - } + common::printIcon('execution', 'testreport', "executionID=$executionID&objectType=execution&extra=$task->id", '', 'list', 'summary', '', '', false, "data-app='execution'", $this->lang->testreport->common); common::printIcon('testtask', 'edit', "taskID=$task->id", $task, 'list'); common::printIcon('testtask', 'delete', "taskID=$task->id", $task, 'list', 'trash', 'hiddenwin'); } diff --git a/module/execution/view/view.html.php b/module/execution/view/view.html.php index 00cef83f9d..6178455394 100644 --- a/module/execution/view/view.html.php +++ b/module/execution/view/view.html.php @@ -329,7 +329,7 @@
execution->manageProducts;?> - id", strtoupper($lang->more), '', "class='btn btn-link pull-right muted'");?> + type != 'stage') common::printLink('execution', 'manageproducts', "executionID=$execution->id", '', '', "class='btn btn-link pull-right muted'");?>
@@ -344,7 +344,7 @@
- + projectInfo->hasProduct or $execution->projectInfo->model == 'scrum'):?>
execution->linkPlan;?>
diff --git a/module/file/model.php b/module/file/model.php index 9fe5e57900..08523e554a 100755 --- a/module/file/model.php +++ b/module/file/model.php @@ -44,7 +44,7 @@ class fileModel extends model { $files = $this->dao->select('*')->from(TABLE_FILE) ->where('objectType')->eq($objectType) - ->andWhere('objectID')->eq((int)$objectID) + ->andWhere('objectID')->in($objectID) ->andWhere('extra')->ne('editor') ->beginIF($extra)->andWhere('extra')->eq($extra) ->andWhere('deleted')->eq('0') diff --git a/module/group/lang/resource.php b/module/group/lang/resource.php index 278cd6f6ee..fe0961b235 100644 --- a/module/group/lang/resource.php +++ b/module/group/lang/resource.php @@ -237,9 +237,29 @@ $lang->project->methodOrder[185] = 'unlinkMember'; $lang->project->methodOrder[190] = 'manageRepo'; $lang->resource->projectbuild = new stdclass(); -$lang->resource->projectbuild->browse = 'browse'; +$lang->resource->projectbuild->browse = 'browse'; +$lang->resource->projectbuild->create = 'create'; +$lang->resource->projectbuild->edit = 'edit'; +$lang->resource->projectbuild->view = 'view'; +$lang->resource->projectbuild->delete = 'delete'; +$lang->resource->projectbuild->linkStory = 'linkStory'; +$lang->resource->projectbuild->unlinkStory = 'unlinkStory'; +$lang->resource->projectbuild->batchUnlinkStory = 'batchUnlinkStory'; +$lang->resource->projectbuild->linkBug = 'linkBug'; +$lang->resource->projectbuild->unlinkBug = 'unlinkBug'; +$lang->resource->projectbuild->batchUnlinkBug = 'batchUnlinkBug'; -$lang->projectbuild->methodOrder[5] = 'browse'; +$lang->projectbuild->methodOrder[5] = 'browse'; +$lang->projectbuild->methodOrder[10] = 'create'; +$lang->projectbuild->methodOrder[15] = 'edit'; +$lang->projectbuild->methodOrder[20] = 'view'; +$lang->projectbuild->methodOrder[25] = 'delete'; +$lang->projectbuild->methodOrder[30] = 'linkStory'; +$lang->projectbuild->methodOrder[35] = 'unlinkStory'; +$lang->projectbuild->methodOrder[40] = 'batchUnlinkStory'; +$lang->projectbuild->methodOrder[45] = 'linkBug'; +$lang->projectbuild->methodOrder[50] = 'unlinkBug'; +$lang->projectbuild->methodOrder[55] = 'batchUnlinkBug'; $lang->resource->projectplan = new stdclass(); $lang->resource->projectplan->browse = 'browse'; @@ -954,15 +974,17 @@ $lang->resource->build->unlinkBug = 'unlinkBug'; $lang->resource->build->batchUnlinkBug = 'batchUnlinkBug'; $lang->build->methodOrder[5] = 'create'; -$lang->build->methodOrder[10] = 'edit'; -$lang->build->methodOrder[15] = 'delete'; -$lang->build->methodOrder[20] = 'view'; -$lang->build->methodOrder[25] = 'linkStory'; -$lang->build->methodOrder[30] = 'unlinkStory'; -$lang->build->methodOrder[35] = 'batchUnlinkStory'; -$lang->build->methodOrder[40] = 'linkBug'; -$lang->build->methodOrder[45] = 'unlinkBug'; -$lang->build->methodOrder[50] = 'batchUnlinkBug'; +$lang->build->methodOrder[10] = 'createProjectBuild'; +$lang->build->methodOrder[15] = 'edit'; +$lang->build->methodOrder[20] = 'editProjectBuild'; +$lang->build->methodOrder[25] = 'delete'; +$lang->build->methodOrder[30] = 'view'; +$lang->build->methodOrder[35] = 'linkStory'; +$lang->build->methodOrder[40] = 'unlinkStory'; +$lang->build->methodOrder[45] = 'batchUnlinkStory'; +$lang->build->methodOrder[50] = 'linkBug'; +$lang->build->methodOrder[55] = 'unlinkBug'; +$lang->build->methodOrder[60] = 'batchUnlinkBug'; /* QA. */ $lang->resource->qa = new stdclass(); diff --git a/module/my/view/bug.html.php b/module/my/view/bug.html.php index fb4e5ccc5f..a0e573d5a7 100644 --- a/module/my/view/bug.html.php +++ b/module/my/view/bug.html.php @@ -62,9 +62,9 @@ idAB);?> +
- @@ -121,10 +121,6 @@ - - - - - - hasProduct) echo "class='hide'";?> > + + + + + diff --git a/module/projectrelease/view/edit.html.php b/module/projectrelease/view/edit.html.php index d453b11af0..2d287d373a 100644 --- a/module/projectrelease/view/edit.html.php +++ b/module/projectrelease/view/edit.html.php @@ -37,7 +37,7 @@ - + diff --git a/module/projectrelease/view/linkstory.html.php b/module/projectrelease/view/linkstory.html.php index 36f169da5f..8cf4b18b1b 100644 --- a/module/projectrelease/view/linkstory.html.php +++ b/module/projectrelease/view/linkstory.html.php @@ -51,7 +51,7 @@ diff --git a/module/projectrelease/view/view.html.php b/module/projectrelease/view/view.html.php index d59bada8f3..23b8bdf03c 100644 --- a/module/projectrelease/view/view.html.php +++ b/module/projectrelease/view/view.html.php @@ -80,7 +80,7 @@ $story):?> - createLink('story', 'view', "storyID=$story->id&version=0¶m=$projectID", '', true);?> + createLink('story', 'view', "storyID=$story->id&version=0¶m={$this->session->project}", '', true);?> - @@ -375,17 +382,11 @@
files?>
files) + if($release->files) echo $this->fetch('file', 'printFiles', array('files' => $release->files, 'fieldset' => 'false')); + foreach($release->buildInfos as $buildID => $buildInfo) { - echo $this->fetch('file', 'printFiles', array('files' => $release->files, 'fieldset' => 'false')); - } - elseif($release->filePath) - { - echo $lang->release->filePath . html::a($release->filePath, $release->filePath, '_blank'); - } - elseif($release->scmPath) - { - echo $lang->release->scmPath . html::a($release->scmPath, $release->scmPath, '_blank'); + if($buildInfo->filePath) echo $lang->release->filePath . html::a($buildInfo->filePath, $buildInfo->filePath, '_blank'); + if($buildInfo->scmPath) echo $lang->release->scmPath . html::a($buildInfo->scmPath, $buildInfo->scmPath, '_blank'); } ?>
diff --git a/module/release/control.php b/module/release/control.php index 703873896b..6327b37398 100644 --- a/module/release/control.php +++ b/module/release/control.php @@ -70,21 +70,21 @@ class release extends control { $releaseID = $this->release->create($productID, $branch); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + $this->loadModel('action')->create('release', $releaseID, 'opened'); - $message = $this->executeHooks($releaseID); - if($message) $this->lang->saveSuccess = $message; + $result = $this->executeHooks($releaseID); + $message = $result ? $result : $this->lang->saveSuccess; - if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $releaseID)); - if(isonlybody()) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'callback' => "parent.loadProductBuilds($productID)")); + if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $message, 'id' => $releaseID)); + if(isonlybody()) return $this->send(array('result' => 'success', 'message' => $message, 'closeModal' => true, 'callback' => "parent.loadProductBuilds($productID)")); - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('view', "releaseID=$releaseID"))); + return $this->send(array('result' => 'success', 'message' => $message, 'locate' => inlink('view', "releaseID=$releaseID"))); } $builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'notrunk|withbranch', 0, 'execution', '', false); $releasedBuilds = $this->release->getReleasedBuilds($productID, $branch); foreach($releasedBuilds as $build) unset($builds[$build]); - unset($builds['trunk']); /* Get the builds of the linked stories or bugs. */ $notEmptyBuilds = array(); @@ -126,10 +126,10 @@ class release extends control if(!empty($changes)) $this->action->logHistory($actionID, $changes); } - $message = $this->executeHooks($releaseID); - if($message) $this->lang->saveSuccess = $message; + $result = $this->executeHooks($releaseID); + $message = $result ? $result : $this->lang->saveSuccess; - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('view', "releaseID=$releaseID"))); + return $this->send(array('result' => 'success', 'message' => $message, 'locate' => inlink('view', "releaseID=$releaseID"))); } $this->loadModel('story'); $this->loadModel('bug'); @@ -137,21 +137,18 @@ class release extends control /* Get release and build. */ $release = $this->release->getById((int)$releaseID); - $this->commonAction($release->product, $release->branch); - $build = $this->build->getById($release->build); + $this->commonAction($release->product); - $builds = $this->loadModel('build')->getBuildPairs($release->product, $release->branch, 'notrunk|withbranch', $release->project, 'project', $release->build, false); - $releasedBuilds = $this->release->getReleasedBuilds($release->product, $release->branch); + $builds = $this->loadModel('build')->getBuildPairs($release->product, $release->branch, 'notrunk|withbranch', 0, 'project', $release->build, false); + $releasedBuilds = $this->release->getReleasedBuilds($release->product); foreach($releasedBuilds as $releasedBuild) { - if($releasedBuild != $build->id) unset($builds[$releasedBuild]); + if(strpos(',' . trim($release->build, ',') . ',', ",{$releasedBuild},") === false) unset($builds[$releasedBuild]); } - unset($builds['trunk']); $this->view->title = $this->view->product->name . $this->lang->colon . $this->lang->release->edit; $this->view->position[] = $this->lang->release->edit; $this->view->release = $release; - $this->view->build = $build; $this->view->builds = $builds; $this->view->users = $this->loadModel('user')->getPairs('noclosed'); @@ -201,7 +198,7 @@ class release extends control $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story'); $stages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->in(array_keys($stories))->andWhere('branch')->eq($release->branch)->fetchPairs('story', 'stage'); - foreach($stages as $storyID => $stage)$stories[$storyID]->stage = $stage; + foreach($stages as $storyID => $stage) $stories[$storyID]->stage = $stage; $bugPager = new pager($type == 'bug' ? $recTotal : 0, $recPerPage, $type == 'bug' ? $pageID : 1); $bugs = $this->dao->select('*')->from(TABLE_BUG) diff --git a/module/release/css/browse.css b/module/release/css/browse.css index 252ff7eb52..8c33b9fd78 100644 --- a/module/release/css/browse.css +++ b/module/release/css/browse.css @@ -1,5 +1,6 @@ #releaseList {font-size: 13px;} td {overflow: hidden; text-overflow: ellipsis; white-space: nowrap;} +.main-table tbody>tr>td:first-child{padding: 2px 8px!important;} .c-id {width: 60px} .c-branch {width: 100px} diff --git a/module/release/js/create.js b/module/release/js/create.js index 77bfc50522..1b64abc7c2 100644 --- a/module/release/js/create.js +++ b/module/release/js/create.js @@ -1,10 +1,21 @@ $('#submit').click(function() { - var dateFormat = new RegExp(/^\d{4}\-\d{2}\-\d{2}$/); - var name = $('#name').val(); - var date = $('#date').val(); - var build = $('#build').val(); - if(name && build && typeof(notEmptyBuilds[build]) != 'undefined' && dateFormat.test(date)) + var dateFormat = new RegExp(/^\d{4}\-\d{2}\-\d{2}$/); + var name = $('#name').val(); + var date = $('#date').val(); + var build = $('#build').val(); + var notEmptyBuild = false; + + $.each(build, function(index, value) + { + if(typeof(notEmptyBuilds[value]) != 'undefined') + { + notEmptyBuild = true; + return false; + } + }) + + if(name && build && notEmptyBuild && dateFormat.test(date)) { var result = confirm(confirmLink) ? true : false; $('#sync').val(result); diff --git a/module/release/model.php b/module/release/model.php index 212bf022e7..92d8aaa8d4 100644 --- a/module/release/model.php +++ b/module/release/model.php @@ -23,19 +23,20 @@ class releaseModel extends model */ public function getByID($releaseID, $setImgSize = false) { - $release = $this->dao->select('t1.*, t2.id as buildID, t2.filePath, t2.scmPath, t2.name as buildName, t2.project, t3.name as productName, t3.type as productType') + $release = $this->dao->select('t1.*, t2.name as productName, t2.type as productType') ->from(TABLE_RELEASE)->alias('t1') - ->leftJoin(TABLE_BUILD)->alias('t2')->on('t1.build = t2.id') - ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id') + ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id') ->where('t1.id')->eq((int)$releaseID) ->orderBy('t1.id DESC') ->fetch(); if(!$release) return false; + $release->builds = $this->dao->select('id, filePath, scmPath, name, project')->from(TABLE_BUILD)->where('id')->in($release->build)->fetchAll(); + $this->loadModel('file'); $release = $this->file->replaceImgURL($release, 'desc'); $release->files = $this->file->getByObject('release', $releaseID); - if(empty($release->files))$release->files = $this->file->getByObject('build', $release->buildID); + if(empty($release->files))$release->files = $this->file->getByObject('build', $release->build); if($setImgSize) $release->desc = $this->file->setImgSize($release->desc); return $release; } @@ -53,19 +54,38 @@ class releaseModel extends model */ public function getList($productID, $branch = 'all', $type = 'all', $orderBy = 't1.date_desc', $pager = null) { - return $this->dao->select('t1.*, t2.name as productName, t3.id as buildID, t3.name as buildName, t3.project, t4.name as projectName') + $releases = $this->dao->select('t1.*, t2.name as productName') ->from(TABLE_RELEASE)->alias('t1') ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id') - ->leftJoin(TABLE_BUILD)->alias('t3')->on('t1.build = t3.id') - ->leftJoin(TABLE_PROJECT)->alias('t4')->on('t1.project = t4.id') ->where('t1.deleted')->eq(0) ->beginIF($productID)->andWhere('t1.product')->eq((int)$productID)->fi() - ->beginIF($branch !== 'all')->andWhere('t1.branch')->eq($branch)->fi() + ->beginIF($branch !== 'all')->andWhere('t1.branch')->like(",{$branch},")->fi() ->beginIF($type != 'all' && $type != 'review')->andWhere('t1.status')->eq($type)->fi() ->beginIF($type == 'review')->andWhere("FIND_IN_SET('{$this->app->user->account}', t1.reviewers)")->fi() ->orderBy($orderBy) ->page($pager) ->fetchAll(); + + $builds = $this->dao->select("t1.id, t1.name, IF(t2.name IS NOT NULL, t2.name, '') AS projectName, IF(t3.name IS NOT NULL, t3.name, '{$this->lang->trunk}') AS branchName") + ->from(TABLE_BUILD)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') + ->leftJoin(TABLE_BRANCH)->alias('t3')->on('t1.branch = t3.id') + ->fetchAll('id'); + + foreach($releases as $release) + { + $releaseBuilds = array(); + foreach(explode(',', $release->build) as $buildID) + { + if(!$buildID or !isset($builds[$buildID])) continue; + + $releaseBuilds[] = $builds[$buildID]; + } + + $release->builds = $releaseBuilds; + } + + return $releases; } /** @@ -96,12 +116,19 @@ class releaseModel extends model */ public function getReleasedBuilds($productID, $branch = 'all') { - $releases = $this->dao->select('build')->from(TABLE_RELEASE) + $builds = $this->dao->select('build')->from(TABLE_RELEASE) ->where('deleted')->eq(0) ->andWhere('product')->eq($productID) ->beginIF($branch !== 'all')->andWhere('branch')->eq($branch)->fi() - ->fetchAll('build'); - return array_keys($releases); + ->fetchPairs('build'); + + $buildIDList = array(); + foreach($builds as $build) + { + $build = explode(',', $build); + $buildIDList = array_merge($buildIDList, $build); + } + return $buildIDList; } /** @@ -146,6 +173,7 @@ class releaseModel extends model ->setDefault('stories', '') ->setDefault('createdBy', $this->app->user->account) ->setDefault('createdDate', helper::now()) + ->join('build', ',') ->join('stories', ',') ->join('bugs', ',') ->join('mailto', ',') @@ -196,14 +224,24 @@ class releaseModel extends model if($release->build) { - $buildInfo = $this->dao->select('project, branch, stories, bugs')->from(TABLE_BUILD)->where('id')->eq($release->build)->fetch(); - $release->branch = $buildInfo->branch; - $release->project = $buildInfo->project; - if($this->post->sync == 'true') + $builds = $this->dao->select('project, branch, stories, bugs')->from(TABLE_BUILD)->where('id')->in($release->build)->fetchAll(); + foreach($builds as $build) { - $release->stories = $buildInfo->stories; - $release->bugs = $buildInfo->bugs; + $branches[$build->branch] = $build->branch; + $projects[$build->project] = $build->project; + + if($this->post->sync == 'true') + { + $build->stories = trim($build->stories, ','); + $build->bugs = trim($build->bugs, ','); + if($build->stories) $release->stories .= ',' . $build->stories; + if($build->bugs) $release->bugs .= ',' . $build->bugs; + } } + + $release->build = ',' . trim($release->build, ',') . ','; + $release->branch = ',' . trim(implode(',', $branches), ',') . ','; + $release->project = ',' . trim(implode(',', $projects), ',') . ','; } $release = $this->loadModel('file')->processImgURL($release, $this->config->release->editor->create['id'], $this->post->uid); $this->dao->insert(TABLE_RELEASE)->data($release) @@ -264,13 +302,12 @@ class releaseModel extends model /* Init vars. */ $releaseID = (int)$releaseID; $oldRelease = $this->getById($releaseID); - $branch = $this->dao->select('branch')->from(TABLE_BUILD)->where('id')->eq((int)$this->post->build)->fetch('branch'); $release = fixer::input('post')->stripTags($this->config->release->editor->edit['id'], $this->config->allowedTags) ->add('id', $releaseID) - ->add('branch', (int)$branch) ->setDefault('mailto', '') ->setDefault('deleteFiles', array()) + ->join('build', ',') ->join('mailto', ',') ->setIF(!$this->post->marker, 'marker', 0) ->cleanInt('product') @@ -282,9 +319,15 @@ class releaseModel extends model /* update release project and branch */ if($release->build) { - $buildInfo = $this->dao->select('project, branch')->from(TABLE_BUILD)->where('id')->eq($release->build)->fetch(); - $release->branch = $buildInfo->branch; - $release->project = $buildInfo->project; + $builds = $this->dao->select('project, branch')->from(TABLE_BUILD)->where('id')->in($release->build)->fetchAll(); + foreach($builds as $build) + { + $branches[$build->branch] = $build->branch; + $projects[$build->project] = $build->project; + } + $release->build = ',' . trim($release->build, ',') . ','; + $release->branch = ',' . trim(implode(',', $branches), ',') . ','; + $release->project = ',' . trim(implode(',', $projects), ',') . ','; } $this->dao->update(TABLE_RELEASE)->data($release, 'deleteFiles') diff --git a/module/release/view/browse.html.php b/module/release/view/browse.html.php index d8e18d56f6..5552c7eb28 100644 --- a/module/release/view/browse.html.php +++ b/module/release/view/browse.html.php @@ -46,16 +46,13 @@

-
resolvedDate) ? '' : substr($bug->resolvedDate, 5, 11);?> id&bug=$bug->id"); + $unlinkURL = $this->createLink($module, 'unlinkBug', "buildID=$build->id&bug=$bug->id"); echo html::a("###", '', '', "onclick='ajaxDelete(\"$unlinkURL\", \"bugList\", confirmUnlinkBug)' class='btn' title='{$lang->build->unlinkBug}'"); } ?> @@ -330,6 +332,23 @@ tbody tr td:first-child input {display: none;} build->name;?> name;?>
build->execution;?>execution);?>
build->builds;?> + + builds) as $buildID):?> + createLink('build', 'view', "buildID=$buildID") . "#app={$app->tab}", zget($buildPairs, $buildID)) . $lang->comma;?> + + comma);?> +
build->builder;?> builder);?>
lang->custom->scrum->common, $disabledScrumFeatures);?>
idAB);?> design->name);?> design->product);?> design->type);?> design->assignedTo);?> design->createdByAB);?>
id);?> createLink('design', 'view', "id={$design->id}"), $design->name) : $design->name;?> product);?> design->typeList, $design->type);?> design->printAssignedHtml($design, $users);?> createdBy);?> {$execution->productName}" . zget($this->lang->project->statusList, $execution->status) . '' . zget($users, $execution->PM) . '' . $execution->begin . '
+ type != 'normal' and isset($branchGroups[$product->id]));?> id] as $branchID => $branch):?>
- id, "class='form-control chosen' onchange='loadBranches(this)' data-last='" . $product->id . "'");?> + id, "class='form-control chosen' $class onchange='loadBranches(this)' data-last='" . $product->id . "'");?> + id);?> id], $branchID, "class='form-control chosen' onchange=\"loadPlans('#products{$i}', this.value)\"");?>
@@ -147,12 +150,14 @@ +
+
execution->manageProducts;?> - grade == 2 ? "readonly='readonly'" : '';?> + grade == 2 or $execution->type == 'stage') ? "disabled" : '';?>
@@ -160,12 +160,14 @@ + type != 'stage'):?>
+
bug->title);?> bug->severityAB);?> priAB);?>bug->title);?> bug->type);?> bug->product);?> pri?>' title='bug->priList, $bug->pri);?>'>bug->priList, $bug->pri)?> - createLink('bug', 'view', "bugID=$bug->id"), $bug->title, null, "style='color: $bug->color' title='{$bug->title}'");?> - case) echo html::a(helper::createLink('testcase', 'view', "caseID=$bug->case&version=$bug->caseVersion"), "[" . $this->lang->testcase->common . "#$bug->case]", '', "class='bug' title='$bug->case'");?> - bug->typeList, $bug->type, '');?> port = new stdclass(); - -$config->port->fieldList['title'] = ''; -$config->port->fieldList['width'] = ''; -$config->port->fieldList['required'] = 'no'; -$config->port->fieldList['fixed'] = ''; -$config->port->fieldList['control'] = 'input'; -$config->port->fieldList['values'] = ''; -$config->port->fieldList['class'] = ''; -$config->port->fieldList['sort'] = ''; -$config->port->fieldList['dataSource'] = array('module' => '', 'method' => '', 'params' => '', 'pairs' => '', 'lang' => ''); - -$config->port->initFunction = 'title,control,required,'; -$config->port->dateFeilds = 'estStarted,realStarted,deadline,openedDate,assignedDate,finishedDate,canceledDate,closedDate,lastEditedDate,'; -$config->port->datetimeFeilds = ''; -$config->port->listFields = ''; -$config->port->sysLangFields = ',pri,status,type,mode,severity,os,browser,resolution,confirmed,source,reviewResult,stage,change,category'; -$config->port->sysDataFields = 'project,execution,product,user'; -$config->port->userFields = 'assignedTo,openedBy,finishedBy,canceledBy,closedBy,lastEditedBy,lastRunner,resolvedBy,reviewedBy,mailto'; - -$config->port->defaultZeroField = 'severity,pri'; -$config->port->defaultEmptyField = ''; - -$config->port->import = new stdClass(); - -$config->port->dateMatch = '/[1-9]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/'; - -$config->port->hasChildDataFields = explode(',', 'task,story'); - -$config->port->lazyLoading = false; -$config->port->showImportCount = 20; diff --git a/module/port/lang/de.php b/module/port/lang/de.php deleted file mode 100644 index f61d721f20..0000000000 --- a/module/port/lang/de.php +++ /dev/null @@ -1,23 +0,0 @@ -port = new stdclass(); - -$lang->port->reservedWord['id'] = 'ID'; -$lang->port->reservedWord['type'] = 'Type'; -$lang->port->reservedWord['project'] = 'Project'; -$lang->port->reservedWord['execution'] = 'Execution'; -$lang->port->reservedWord['product'] = 'Project'; -$lang->port->reservedWord['code'] = 'CODE'; -$lang->port->reservedWord['name'] = 'Name'; -$lang->port->reservedWord['title'] = 'Title'; - -$lang->port->importCase = 'Import EXCEL'; -$lang->port->importNotice = 'Notice: Importing data is risky! Make sure to complete the following steps in sequence before merging.'; -$lang->port->noRequire = 'Row %s has“%s ”which is a required field and it should not be blank.'; -$lang->port->exportTemplate = 'Export Template'; -$lang->port->import = 'Import'; -$lang->port->id = 'ID'; - -$lang->port->new = 'New'; - -$lang->port->num = 'Count:'; -$lang->port->children = 'Child'; diff --git a/module/port/lang/en.php b/module/port/lang/en.php deleted file mode 100644 index f61d721f20..0000000000 --- a/module/port/lang/en.php +++ /dev/null @@ -1,23 +0,0 @@ -port = new stdclass(); - -$lang->port->reservedWord['id'] = 'ID'; -$lang->port->reservedWord['type'] = 'Type'; -$lang->port->reservedWord['project'] = 'Project'; -$lang->port->reservedWord['execution'] = 'Execution'; -$lang->port->reservedWord['product'] = 'Project'; -$lang->port->reservedWord['code'] = 'CODE'; -$lang->port->reservedWord['name'] = 'Name'; -$lang->port->reservedWord['title'] = 'Title'; - -$lang->port->importCase = 'Import EXCEL'; -$lang->port->importNotice = 'Notice: Importing data is risky! Make sure to complete the following steps in sequence before merging.'; -$lang->port->noRequire = 'Row %s has“%s ”which is a required field and it should not be blank.'; -$lang->port->exportTemplate = 'Export Template'; -$lang->port->import = 'Import'; -$lang->port->id = 'ID'; - -$lang->port->new = 'New'; - -$lang->port->num = 'Count:'; -$lang->port->children = 'Child'; diff --git a/module/port/lang/fr.php b/module/port/lang/fr.php deleted file mode 100644 index f61d721f20..0000000000 --- a/module/port/lang/fr.php +++ /dev/null @@ -1,23 +0,0 @@ -port = new stdclass(); - -$lang->port->reservedWord['id'] = 'ID'; -$lang->port->reservedWord['type'] = 'Type'; -$lang->port->reservedWord['project'] = 'Project'; -$lang->port->reservedWord['execution'] = 'Execution'; -$lang->port->reservedWord['product'] = 'Project'; -$lang->port->reservedWord['code'] = 'CODE'; -$lang->port->reservedWord['name'] = 'Name'; -$lang->port->reservedWord['title'] = 'Title'; - -$lang->port->importCase = 'Import EXCEL'; -$lang->port->importNotice = 'Notice: Importing data is risky! Make sure to complete the following steps in sequence before merging.'; -$lang->port->noRequire = 'Row %s has“%s ”which is a required field and it should not be blank.'; -$lang->port->exportTemplate = 'Export Template'; -$lang->port->import = 'Import'; -$lang->port->id = 'ID'; - -$lang->port->new = 'New'; - -$lang->port->num = 'Count:'; -$lang->port->children = 'Child'; diff --git a/module/port/lang/zh-cn.php b/module/port/lang/zh-cn.php deleted file mode 100644 index 649ab708bc..0000000000 --- a/module/port/lang/zh-cn.php +++ /dev/null @@ -1,23 +0,0 @@ -port = new stdclass(); - -$lang->port->reservedWord['id'] = '编号'; -$lang->port->reservedWord['type'] = '类型'; -$lang->port->reservedWord['project'] = '所属项目'; -$lang->port->reservedWord['execution'] = '所属执行'; -$lang->port->reservedWord['product'] = '所属产品'; -$lang->port->reservedWord['code'] = '代号'; -$lang->port->reservedWord['name'] = '名称'; -$lang->port->reservedWord['title'] = '标题'; - -$lang->port->importCase = '导入Excel'; -$lang->port->importNotice = '请先导出模板,按照模板格式填写数据后再导入。'; -$lang->port->noRequire = '%s行的“ %s” 是必填字段,不能为空。'; -$lang->port->exportTemplate = '导出模板'; -$lang->port->import = '导入'; -$lang->port->id = '编号'; - -$lang->port->new = '新增'; - -$lang->port->num = '记录数:'; -$lang->port->children = '子任务'; diff --git a/module/product/control.php b/module/product/control.php index e78e365449..d14444e506 100755 --- a/module/product/control.php +++ b/module/product/control.php @@ -23,6 +23,8 @@ class product extends control { parent::__construct($moduleName, $methodName); + if(!isset($this->app->user)) return; + /* Load need modules. */ $this->loadModel('story'); $this->loadModel('release'); @@ -338,7 +340,7 @@ class product extends control $this->view->productName = $productName; $this->view->moduleID = $moduleID; $this->view->stories = $stories; - $this->view->plans = $this->loadModel('productplan')->getPairs($productID, $branch === 'all' ? '' : $branch, '', true); + $this->view->plans = $this->loadModel('productplan')->getPairs($productID, ($branch === 'all' or empty($branch)) ? '' : $branch, '', true); $this->view->productPlans = isset($productPlans) ? array(0 => '') + $productPlans : array(); $this->view->summary = $this->product->summary($stories, $storyType); $this->view->moduleTree = $moduleTree; @@ -880,27 +882,6 @@ class product extends control $this->display(); } - /** - * Ajax get products. - * - * @param int $executionID - * @access public - * @return void - */ - public function ajaxGetProducts($executionID) - { - $this->loadModel('build'); - $products = $this->product->getProductPairsByProject($executionID); - if(empty($products)) - { - return printf($this->lang->build->noProduct, $this->createLink('execution', 'manageproducts', "executionID=$executionID&from=buildCreate", '', 'true'), 'project'); - } - else - { - return print(html::select('product', $products, empty($product) ? '' : $product->id, "onchange='loadBranches(this.value);' class='form-control chosen' required data-toggle='modal' data-type='iframe'")); - } - } - /** * AJAX: get projects of a product in html select. * diff --git a/module/product/model.php b/module/product/model.php index 32b5caae60..5ec816a474 100644 --- a/module/product/model.php +++ b/module/product/model.php @@ -440,7 +440,7 @@ class productModel extends model ->from(TABLE_PROJECTPRODUCT)->alias('t1') ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id') ->where('t2.deleted')->eq(0) - ->beginIF(!empty($projectID))->andWhere('t1.project')->eq($projectID)->fi() + ->beginIF(!empty($projectID))->andWhere('t1.project')->in($projectID)->fi() ->beginIF(!$this->app->user->admin and $this->config->vision == 'rnd')->andWhere('t2.id')->in($views)->fi() ->andWhere('t2.vision')->eq($this->config->vision) ->beginIF(strpos($status, 'noclosed') !== false)->andWhere('t2.status')->ne('closed')->fi() @@ -1137,7 +1137,7 @@ class productModel extends model $this->config->product->search['actionURL'] = $actionURL; $this->config->product->search['queryID'] = $queryID; - $this->config->product->search['params']['plan']['values'] = $this->loadModel('productplan')->getPairs($productIdList, $branchParam); + $this->config->product->search['params']['plan']['values'] = $this->loadModel('productplan')->getPairs($productIdList, (empty($branchParam) or $branchParam == 'all') ? '' : $branchParam); $product = ($this->app->tab == 'project' and empty($productID)) ? $products : array($productID => $products[$productID]); @@ -1398,15 +1398,20 @@ class productModel extends model { if(empty($productID)) return array(); - $project = $this->loadModel('project')->getByID($projectID); - $orderBy = $project && $project->model == 'waterfall' ? 't2.begin_asc,t2.id_asc' : 't2.begin_desc,t2.id_desc'; + $projects = $this->loadModel('project')->getByIdList($projectID); + $hasWaterfall = false; + foreach($projects as $project) + { + if($project->model == 'waterfall') $hasWaterfall = true; + } + $orderBy = $hasWaterfall ? 't2.begin_asc,t2.id_asc' : 't2.begin_desc,t2.id_desc'; $executions = $this->dao->select('t2.id,t2.name,t2.grade,t2.parent,t2.attribute,t2.multiple,t3.name as projectName')->from(TABLE_PROJECTPRODUCT)->alias('t1') ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.project = t2.id') ->leftJoin(TABLE_PROJECT)->alias('t3')->on('t2.project = t3.id') ->where('t1.product')->eq($productID) ->andWhere('t2.type')->in('sprint,kanban,stage') - ->beginIF($projectID)->andWhere('t2.project')->eq($projectID)->fi() + ->beginIF($projectID)->andWhere('t2.project')->in($projectID)->fi() ->beginIF($branch !== '')->andWhere('t1.branch')->in($branch)->fi() ->beginIF(!$this->app->user->admin)->andWhere('t2.id')->in($this->app->user->view->sprints)->fi() ->beginIF(strpos($mode, 'noclosed') !== false)->andWhere('t2.status')->ne('closed')->fi() @@ -1417,7 +1422,7 @@ class productModel extends model /* The waterfall project needs to show the hierarchy and remove the parent stage. */ $executionPairs = array('0' => ''); - if($project && $project->model == 'waterfall') + if($hasWaterfall) { foreach($executions as $id => $execution) { @@ -1922,7 +1927,7 @@ class productModel extends model $allTotal = array_sum($product->stories) + array_sum($product->requirements); $product->progress = empty($closedTotal) ? 0 : round($closedTotal / $allTotal * 100, 1); - $stats[] = $product; + $stats[$key] = $product; } return $stats; @@ -2378,7 +2383,7 @@ class productModel extends model } elseif($module == 'feedback') { - return helper::createLink('feedback', 'admin', "browseType=byProduct&productID=%s"); + return helper::createLink('feedback', $method, "browseType=byProduct&productID=%s"); } elseif($module == 'ticket') { diff --git a/module/product/view/ajaxgetdropmenu.html.php b/module/product/view/ajaxgetdropmenu.html.php index 8de228df5e..a40ef8a9b0 100644 --- a/module/product/view/ajaxgetdropmenu.html.php +++ b/module/product/view/ajaxgetdropmenu.html.php @@ -159,7 +159,7 @@ $closedProductsHtml .= ''; + diff --git a/module/project/view/testtask.html.php b/module/project/view/testtask.html.php index 8fce5a0087..a735daadb3 100644 --- a/module/project/view/testtask.html.php +++ b/module/project/view/testtask.html.php @@ -105,10 +105,7 @@ { common::printIcon('testtask', 'cases', "taskID=$task->id", $task, 'list', 'sitemap'); common::printIcon('testtask', 'linkCase', "taskID=$task->id", $task, 'list', 'link'); - if(common::hasPriv('execution', 'testreport')) - { - echo html::a($this->createLink('execution', 'testreport', "executionID=$task->execution&objectType=execution&extra=$task->id"), '', '', 'class="btn " title="' . $lang->testreport->browse . '" data-app="project"'); - } + common::printIcon('project', 'testreport', "projectID=$task->project&objectType=project&extra=$task->id", '', 'list', 'summary', '', '', false, "data-app='project'", $this->lang->testreport->common); common::printIcon('testtask', 'edit', "taskID=$task->id", $task, 'list'); common::printIcon('testtask', 'delete', "taskID=$task->id", $task, 'list', 'trash', 'hiddenwin'); } diff --git a/module/projectbuild/control.php b/module/projectbuild/control.php index 75f6248eeb..a89ebca02f 100644 --- a/module/projectbuild/control.php +++ b/module/projectbuild/control.php @@ -76,4 +76,144 @@ class projectBuild extends control $this->display(); } + + /** + * Create a build for project. + * + * @param int $projectID + * @access public + * @return void + */ + public function create($projectID = 0) + { + echo $this->fetch('build', 'create', "executionID=0&productID=0&projectID=$projectID"); + } + + /** + * Edit a build for project. + * + * @param int $buildID + * @access public + * @return void + */ + public function edit($buildID) + { + echo $this->fetch('build', 'edit', "buildID=$buildID"); + } + + /** + * View a build for project. + * + * @param int $buildID + * @param string $type + * @param string $link + * @param string $param + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function view($buildID, $type = 'story', $link = 'false', $param = '', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 100, $pageID = 1) + { + echo $this->fetch('build', 'view', "buildID=$buildID&type=$type&link=$link¶m=$param&orderBy=$orderBy&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID"); + } + + /** + * Delete a build for project. + * + * @param int $buildID + * @param string $confirm yes|noe + * @access public + * @return void + */ + public function delete($buildID, $confirm = 'no') + { + echo $this->fetch('build', 'delete', "buildID=$buildID&confirm=$confirm"); + } + + /** + * Link stories. + * + * @param int $buildID + * @param string $browseType + * @param int $param + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function linkStory($buildID = 0, $browseType = '', $param = 0, $recTotal = 0, $recPerPage = 100, $pageID = 1) + { + echo $this->fetch('build', 'linkStory', "buildID=$buildID&browseType=$browseType¶m=$param&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID"); + } + + /** + * Unlink story + * + * @param int $storyID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function unlinkStory($buildID, $storyID) + { + echo $this->fetch('build', 'unlinkStory', "buildID=$buildID&storyID=$storyID"); + } + + /** + * Batch unlink story. + * + * @param string $confirm + * @access public + * @return void + */ + public function batchUnlinkStory($buildID) + { + echo $this->fetch('build', 'batchUnlinkStory', "buildID=$buildID"); + } + + /** + * Link bugs. + * + * @param int $buildID + * @param string $browseType + * @param int $param + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function linkBug($buildID = 0, $browseType = '', $param = 0, $recTotal = 0, $recPerPage = 100, $pageID = 1) + { + echo $this->fetch('build', 'linkBug', "buildID=$buildID&browseType=$browseType¶m=$param&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID"); + } + + /** + * Unlink story + * + * @param int $buildID + * @param int $bugID + * @access public + * @return void + */ + public function unlinkBug($buildID, $bugID) + { + echo $this->fetch('build', 'unlinkBug', "buildID=$buildID&bugID=$bugID"); + } + + /** + * Batch unlink story. + * + * @param int $buildID + * @access public + * @return void + */ + public function batchUnlinkBug($buildID) + { + echo $this->fetch('build', 'batchUnlinkBug', "buildID=$buildID"); + } } diff --git a/module/projectbuild/lang/de.php b/module/projectbuild/lang/de.php index b26ec0d136..b7457dd3d1 100644 --- a/module/projectbuild/lang/de.php +++ b/module/projectbuild/lang/de.php @@ -9,5 +9,16 @@ * @version $Id: zh-cn.php 4129 2020-11-27 01:58:14Z wwccss $ * @link http://www.zentao.net */ -$lang->projectbuild->common = 'Project Build'; -$lang->projectbuild->browse = 'Build List'; +$lang->projectbuild->common = 'Project Build'; +$lang->projectbuild->browse = 'Build List'; +$lang->projectbuild->create = "Create Build"; +$lang->projectbuild->edit = "Edit Build"; +$lang->projectbuild->delete = "Delete Build"; +$lang->projectbuild->view = "Build Detail"; +$lang->projectbuild->linkStory = "Link {$lang->SRCommon}"; +$lang->projectbuild->linkBug = "Link Bug"; +$lang->projectbuild->unlinkStory = "Unlink {$lang->SRCommon}"; +$lang->projectbuild->unlinkBug = "Unlink Bug"; +$lang->projectbuild->batchUnlink = 'Batch Unlink'; +$lang->projectbuild->batchUnlinkStory = "Batch Unlink {$lang->SRCommon}"; +$lang->projectbuild->batchUnlinkBug = 'Batch Unlink Bug'; diff --git a/module/projectbuild/lang/en.php b/module/projectbuild/lang/en.php index b26ec0d136..b7457dd3d1 100644 --- a/module/projectbuild/lang/en.php +++ b/module/projectbuild/lang/en.php @@ -9,5 +9,16 @@ * @version $Id: zh-cn.php 4129 2020-11-27 01:58:14Z wwccss $ * @link http://www.zentao.net */ -$lang->projectbuild->common = 'Project Build'; -$lang->projectbuild->browse = 'Build List'; +$lang->projectbuild->common = 'Project Build'; +$lang->projectbuild->browse = 'Build List'; +$lang->projectbuild->create = "Create Build"; +$lang->projectbuild->edit = "Edit Build"; +$lang->projectbuild->delete = "Delete Build"; +$lang->projectbuild->view = "Build Detail"; +$lang->projectbuild->linkStory = "Link {$lang->SRCommon}"; +$lang->projectbuild->linkBug = "Link Bug"; +$lang->projectbuild->unlinkStory = "Unlink {$lang->SRCommon}"; +$lang->projectbuild->unlinkBug = "Unlink Bug"; +$lang->projectbuild->batchUnlink = 'Batch Unlink'; +$lang->projectbuild->batchUnlinkStory = "Batch Unlink {$lang->SRCommon}"; +$lang->projectbuild->batchUnlinkBug = 'Batch Unlink Bug'; diff --git a/module/projectbuild/lang/fr.php b/module/projectbuild/lang/fr.php index b26ec0d136..b7457dd3d1 100644 --- a/module/projectbuild/lang/fr.php +++ b/module/projectbuild/lang/fr.php @@ -9,5 +9,16 @@ * @version $Id: zh-cn.php 4129 2020-11-27 01:58:14Z wwccss $ * @link http://www.zentao.net */ -$lang->projectbuild->common = 'Project Build'; -$lang->projectbuild->browse = 'Build List'; +$lang->projectbuild->common = 'Project Build'; +$lang->projectbuild->browse = 'Build List'; +$lang->projectbuild->create = "Create Build"; +$lang->projectbuild->edit = "Edit Build"; +$lang->projectbuild->delete = "Delete Build"; +$lang->projectbuild->view = "Build Detail"; +$lang->projectbuild->linkStory = "Link {$lang->SRCommon}"; +$lang->projectbuild->linkBug = "Link Bug"; +$lang->projectbuild->unlinkStory = "Unlink {$lang->SRCommon}"; +$lang->projectbuild->unlinkBug = "Unlink Bug"; +$lang->projectbuild->batchUnlink = 'Batch Unlink'; +$lang->projectbuild->batchUnlinkStory = "Batch Unlink {$lang->SRCommon}"; +$lang->projectbuild->batchUnlinkBug = 'Batch Unlink Bug'; diff --git a/module/projectbuild/lang/vi.php b/module/projectbuild/lang/vi.php index b26ec0d136..b7457dd3d1 100644 --- a/module/projectbuild/lang/vi.php +++ b/module/projectbuild/lang/vi.php @@ -9,5 +9,16 @@ * @version $Id: zh-cn.php 4129 2020-11-27 01:58:14Z wwccss $ * @link http://www.zentao.net */ -$lang->projectbuild->common = 'Project Build'; -$lang->projectbuild->browse = 'Build List'; +$lang->projectbuild->common = 'Project Build'; +$lang->projectbuild->browse = 'Build List'; +$lang->projectbuild->create = "Create Build"; +$lang->projectbuild->edit = "Edit Build"; +$lang->projectbuild->delete = "Delete Build"; +$lang->projectbuild->view = "Build Detail"; +$lang->projectbuild->linkStory = "Link {$lang->SRCommon}"; +$lang->projectbuild->linkBug = "Link Bug"; +$lang->projectbuild->unlinkStory = "Unlink {$lang->SRCommon}"; +$lang->projectbuild->unlinkBug = "Unlink Bug"; +$lang->projectbuild->batchUnlink = 'Batch Unlink'; +$lang->projectbuild->batchUnlinkStory = "Batch Unlink {$lang->SRCommon}"; +$lang->projectbuild->batchUnlinkBug = 'Batch Unlink Bug'; diff --git a/module/projectbuild/lang/zh-cn.php b/module/projectbuild/lang/zh-cn.php index 9778b8b404..4140a633b1 100644 --- a/module/projectbuild/lang/zh-cn.php +++ b/module/projectbuild/lang/zh-cn.php @@ -9,5 +9,16 @@ * @version $Id: zh-cn.php 4129 2020-11-27 01:58:14Z wwccss $ * @link http://www.zentao.net */ -$lang->projectbuild->common = '项目版本'; -$lang->projectbuild->browse = '版本列表'; +$lang->projectbuild->common = '项目版本'; +$lang->projectbuild->browse = '版本列表'; +$lang->projectbuild->create = "创建项目版本"; +$lang->projectbuild->edit = "编辑项目版本"; +$lang->projectbuild->delete = "删除版本"; +$lang->projectbuild->view = "版本详情"; +$lang->projectbuild->linkStory = "关联{$lang->SRCommon}"; +$lang->projectbuild->linkBug = "关联Bug"; +$lang->projectbuild->unlinkStory = "移除{$lang->SRCommon}"; +$lang->projectbuild->unlinkBug = "移除Bug"; +$lang->projectbuild->batchUnlink = '批量移除'; +$lang->projectbuild->batchUnlinkStory = "批量移除{$lang->SRCommon}"; +$lang->projectbuild->batchUnlinkBug = '批量移除Bug'; diff --git a/module/projectrelease/control.php b/module/projectrelease/control.php index 836240b2bd..616a6acfbf 100644 --- a/module/projectrelease/control.php +++ b/module/projectrelease/control.php @@ -49,7 +49,7 @@ class projectrelease extends control $this->view->products = $this->products; $this->view->product = $product; - $this->view->branches = (isset($product->type) and $product->type == 'normal') ? array() : $this->loadModel('branch')->getPairs($productID, 'active'); + $this->view->branches = (isset($product->type) and $product->type == 'normal') ? array() : $this->loadModel('branch')->getPairs($productID, 'active', $projectID); $this->view->branch = $branch; $this->view->project = $this->project->getByID($projectID); } @@ -82,19 +82,20 @@ class projectrelease extends control $this->app->loadClass('pager', $static = true); $pager = new pager($recTotal, $recPerPage, $pageID); + $releases = $this->projectrelease->getList($projectID, $type, $orderBy, $pager); + $productIdList = array(); + foreach($releases as $release) $productIdList[$release->product] = $release->product; + $this->view->title = $objectName . $this->lang->colon . $this->lang->release->browse; - $this->view->position[] = $this->lang->release->browse; $this->view->execution = $execution; $this->view->project = $project; $this->view->products = $this->loadModel('product')->getProducts($projectID); - $this->view->releases = $this->projectrelease->getList($projectID, $type, $orderBy, $pager); + $this->view->releases = $releases; + $this->view->builds = $this->loadModel('build')->getBuildPairs($productIdList, 'all', 'notrunk,withbranch', $projectID, 'project'); $this->view->projectID = $projectID; $this->view->executionID = $executionID; $this->view->type = $type; $this->view->from = $this->app->tab; - $this->view->recTotal = $recTotal; - $this->view->recPerPage = $recPerPage; - $this->view->pageID = $pageID; $this->view->pager = $pager; $this->display(); } @@ -132,7 +133,7 @@ class projectrelease extends control $this->commonAction($projectID); /* Get the builds that can select. */ - $builds = $this->build->getBuildPairs(array_keys($this->products), 'all', 'notrunk,withbranch', $projectID, 'project'); + $builds = $this->build->getBuildPairs($this->view->product->id, $this->view->branch, 'notrunk,withbranch', $projectID, 'project'); $releasedBuilds = $this->projectrelease->getReleasedBuilds($projectID); foreach($releasedBuilds as $build) unset($builds[$build]); unset($builds['trunk']); @@ -183,25 +184,29 @@ class projectrelease extends control /* Get release and build. */ $release = $this->projectrelease->getById((int)$releaseID); - $this->commonAction($release->project, $release->product, $release->branch); - $build = $this->build->getById($release->build); + if(!$this->session->project) $this->session->set('project', explode(',', trim($release->project, ','))[0], 'project'); + + $this->commonAction($this->session->project, $release->product, $release->branch); + $bindBuilds = $this->build->getByList($release->build); /* Get the builds that can select. */ - $builds = $this->build->getBuildPairs($release->product, $release->branch, 'notrunk|withbranch', $release->project, 'project', '', false); - $releasedBuilds = $this->projectrelease->getReleasedBuilds($release->project); + $builds = $this->build->getBuildPairs($release->product, $release->branch, 'notrunk|withbranch', $this->session->project, 'project', '', false); + $releasedBuilds = $this->projectrelease->getReleasedBuilds($this->session->project); foreach($releasedBuilds as $releasedBuild) { - if($releasedBuild != $build->id) unset($builds[$releasedBuild]); + foreach(explode(',', trim($releasedBuild, ',')) as $bindBuildID) + { + if(!isset($bindBuilds[$bindBuildID])) unset($builds[$bindBuildID]); + } } unset($builds['trunk']); /* Set project menu. */ - $this->project->setMenu($release->project); + $this->project->setMenu($this->session->project); $this->view->title = $this->view->product->name . $this->lang->colon . $this->lang->release->edit; $this->view->position[] = $this->lang->release->edit; $this->view->release = $release; - $this->view->build = $build; $this->view->builds = $builds; $this->view->users = $this->loadModel('user')->getPairs('noclosed'); @@ -276,11 +281,11 @@ class projectrelease extends control ->fetchAll(); $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'leftBugs'); - $this->commonAction($release->project, $release->product); + $this->commonAction($this->session->project, $release->product); $product = $this->product->getById($release->product); /* Set menu. */ - $this->project->setMenu($release->project); + $this->project->setMenu($this->session->project); $this->executeHooks($releaseID); @@ -300,7 +305,6 @@ class projectrelease extends control $this->view->storyPager = $storyPager; $this->view->bugPager = $bugPager; $this->view->leftBugPager = $leftBugPager; - $this->view->projectID = $release->project; $this->display(); } @@ -327,7 +331,7 @@ class projectrelease extends control } $release = $this->release->getByID($releaseID); - $project = $this->loadModel('project')->getByID($release->project); + $project = $this->loadModel('project')->getByID($this->session->project); if(!$project->hasProduct) { @@ -361,11 +365,15 @@ class projectrelease extends control } else { + $this->loadModel('build'); $this->release->delete(TABLE_RELEASE, $releaseID); $release = $this->dao->select('*')->from(TABLE_RELEASE)->where('id')->eq((int)$releaseID)->fetch(); - $build = $this->dao->select('*')->from(TABLE_BUILD)->where('id')->eq((int)$release->build)->fetch(); - if(empty($build->execution)) $this->loadModel('build')->delete(TABLE_BUILD, $build->id); + $builds = $this->dao->select('*')->from(TABLE_BUILD)->where('id')->in($release->build)->fetchAll('id'); + foreach($builds as $build) + { + if(empty($build->execution)) $this->build->delete(TABLE_BUILD, $build->id); + } $message = $this->executeHooks($releaseID); if($message) $response['message'] = $message; @@ -382,8 +390,7 @@ class projectrelease extends control { $response['result'] = 'success'; $response['message'] = ''; - $release = $this->release->getById($releaseID); - $this->dao->update(TABLE_BUILD)->set('deleted')->eq(1)->where('id')->eq($release->build)->andWhere('name')->eq($release->name)->exec(); + $this->dao->update(TABLE_BUILD)->set('deleted')->eq(1)->where('id')->in($release->build)->andWhere('name')->eq($release->name)->exec(); } return $this->send($response); } @@ -522,9 +529,11 @@ class projectrelease extends control $this->session->set('storyList', inlink('view', "releaseID=$releaseID&type=story&link=true¶m=" . helper::safe64Encode("&browseType=$browseType&queryID=$param")), $this->app->tab); $release = $this->projectrelease->getByID($releaseID); - $build = $this->loadModel('build')->getByID($release->build); - $project = $this->loadModel('project')->getByID($release->project); - $this->commonAction($release->project, $release->product); + if(!$this->session->project) $this->session->set('project', explode(',', trim($release->project, ','))[0], 'project'); + + $builds = $this->loadModel('build')->getByList($release->build); + $project = $this->loadModel('project')->getByIdList($this->session->project); + $this->commonAction($this->session->project, $release->product); $this->loadModel('story'); $this->loadModel('tree'); $this->loadModel('product'); @@ -552,20 +561,29 @@ class projectrelease extends control else { $this->config->product->search['fields']['branch'] = sprintf($this->lang->product->branch, $this->lang->product->branchName[$release->productType]); - $branchName = $this->loadModel('branch')->getById($release->branch); - $branches = array('' => '', BRANCH_MAIN => $this->lang->branch->main, $release->branch => $branchName); + $allBranchs = $this->loadModel('branch')->getPairs($release->product); + $branches = array('' => '', BRANCH_MAIN => $this->lang->branch->main); + foreach(explode(',', trim($release->branch, ',')) as $branchID) $branches[$branchID] = zget($allBranchs, $branchID); $this->config->product->search['params']['branch']['values'] = $branches; } if($this->view->project->model == 'waterfall' && empty($this->view->project->hasProduct)) unset($this->config->product->search['fields']['plan']); $this->loadModel('search')->setSearchParams($this->config->product->search); + $executionIdList = array(); + foreach($builds as $build) + { + if(empty($build->execution)) continue; + $executionIdList[$build->execution] = $build->execution; + } + + $allStories = array(); if($browseType == 'bySearch') { - $allStories = $this->story->getBySearch($release->product, $release->branch, $queryID, 'id', $build->execution ? $build->execution : '', 'story', $release->stories, $pager); + $allStories = $this->story->getBySearch($release->product, $release->branch, $queryID, 'id', $executionIdList, 'story', $release->stories, $pager); } else { - $allStories = $this->story->getExecutionStories($build->execution, $release->product, 0, 't1.`order`_desc', 'byBranch', $release->branch, 'story', $release->stories, $pager); + $allStories = $this->story->getExecutionStories($executionIdList, $release->product, 0, 't1.`order`_desc', 'byBranch', $release->branch, 'story', $release->stories, $pager); } $this->view->allStories = $allStories; @@ -575,7 +593,6 @@ class projectrelease extends control $this->view->browseType = $browseType; $this->view->param = $param; $this->view->pager = $pager; - $this->view->projectID = $release->project; $this->display(); } @@ -646,9 +663,11 @@ class projectrelease extends control $this->session->set('bugList', inlink('view', "releaseID=$releaseID&type=$type&link=true¶m=" . helper::safe64Encode("&browseType=$browseType&queryID=$param")), 'qa'); /* Set menu. */ $release = $this->projectrelease->getByID($releaseID); - $build = $this->loadModel('build')->getByID($release->build); - $project = $this->loadModel('project')->getByID($release->project); - $this->commonAction($release->project, $release->product); + if(!$this->session->project) $this->session->set('project', explode(',', trim($release->project, ','))[0], 'project'); + + $builds = $this->loadModel('build')->getByList($release->build); + $project = $this->loadModel('project')->getByID($this->session->project); + $this->commonAction($this->session->project, $release->product); /* Load pager. */ $this->app->loadClass('pager', $static = true); @@ -676,8 +695,9 @@ class projectrelease extends control else { $this->config->bug->search['fields']['branch'] = sprintf($this->lang->product->branch, $this->lang->product->branchName[$release->productType]); - $branchName = $this->loadModel('branch')->getById($release->branch); - $branches = array('' => '', BRANCH_MAIN => $this->lang->branch->main, $release->branch => $branchName); + $allBranchs = $this->loadModel('branch')->getPairs($release->product); + $branches = array('' => '', BRANCH_MAIN => $this->lang->branch->main); + foreach(explode(',', trim($release->branch, ',')) as $branchID) $branches[$branchID] = zget($allBranchs, $branchID); $this->config->bug->search['params']['branch']['values'] = $branches; } if($this->view->project->model == 'waterfall' && empty($this->view->project->hasProduct)) unset($this->config->bug->search['fields']['plan']); @@ -689,15 +709,15 @@ class projectrelease extends control { $allBugs = $this->bug->getBySearch($release->product, $release->branch, $queryID, 'id_desc', $releaseBugs, $pager); } - elseif($build->execution) + else { if($type == 'bug') { - $allBugs = $this->bug->getReleaseBugs($build->id, $release->product, $release->branch, $releaseBugs, $pager); + $allBugs = $this->bug->getReleaseBugs(array_keys($builds), $release->product, $release->branch, $releaseBugs, $pager); } elseif($type == 'leftBug') { - $allBugs = $this->bug->getProductLeftBugs($build->id, $release->product, $release->branch, $releaseBugs, $pager); + $allBugs = $this->bug->getProductLeftBugs(array_keys($builds), $release->product, $release->branch, $releaseBugs, $pager); } } @@ -772,4 +792,23 @@ class projectrelease extends control $actionID = $this->loadModel('action')->create('release', $releaseID, 'changestatus', '', $status); return print(js::reload('parent')); } + + /** + * Ajax load builds. + * + * @param int $projectID + * @param int $productID + * @param int $branch + * @access public + * @return void + */ + public function ajaxLoadBuilds($projectID, $productID, $branch = 0) + { + $builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'notrunk,withbranch', $projectID, 'project'); + $releasedBuilds = $this->projectrelease->getReleasedBuilds($projectID); + foreach($releasedBuilds as $build) unset($builds[$build]); + unset($builds['trunk']); + + return print(html::select('build[]', $builds, '', "class='form-control chosen' multiple")); + } } diff --git a/module/projectrelease/css/browse.css b/module/projectrelease/css/browse.css index fb0a3a0c39..8f2f032ebf 100644 --- a/module/projectrelease/css/browse.css +++ b/module/projectrelease/css/browse.css @@ -1,5 +1,7 @@ #releaseList {font-size: 13px;} +#releaseList tr:hover {background-color:unset;} td {overflow: hidden; text-overflow: ellipsis; white-space: nowrap;} -.c-build {width: 100px;} +.c-build {width: 250px;} +td.c-build {padding-left:8px !important;} .c-product, .c-execution {width: 120px;} diff --git a/module/projectrelease/js/common.js b/module/projectrelease/js/common.js index 2b067af8df..1f3ddf85b3 100644 --- a/module/projectrelease/js/common.js +++ b/module/projectrelease/js/common.js @@ -2,41 +2,3 @@ $(document).ready(function() { $("a.preview").modalTrigger({width:1000, type:'iframe'}); }) - -/** - * Determined whether to show the productis. - * - * @param int $build - * @access public - * @return void - */ -function showProducts(build) -{ - if(build) $('#productBox').hide(); - if(!build) $('#productBox').show(); -} - -/** - * Flush the branch when switching products. - * - * @param int $productID - * @access public - * @return void - */ -function loadBranches(productID) -{ - $('#branch').remove(); - $('#branch_chosen').remove(); - $.get(createLink('branch', 'ajaxGetBranches', "productID=" + productID + '&oldBranch=0¶ms=active&projectID=' + projectID), function(data) - { - var $product = $('#product'); - var $inputGroup = $product.closest('.input-group'); - $inputGroup.find('.input-group-addon').toggleClass('hidden', !data); - if(data) - { - $inputGroup.append(data); - $('#branch').css('width', '120px').chosen(); - } - $inputGroup.fixInputGroup(); - }) -} diff --git a/module/projectrelease/js/create.js b/module/projectrelease/js/create.js index 41e0964444..e04f96c37c 100644 --- a/module/projectrelease/js/create.js +++ b/module/projectrelease/js/create.js @@ -12,3 +12,43 @@ $('#submit').click(function() }); $('[data-toggle="popover"]').popover(); + +/** + * Ajax load unlinked builds with project and product. + * + * @access public + * @return void + */ +function loadBuilds() +{ + var productID = $('#product').val(); + var branch = $('#branch').length == 0 ? 0 : $('#branch').val(); + $('#buildBox').load(createLink('projectrelease', 'ajaxLoadBuilds', "projectID=" + projectID + "&productID=" + productID + "&branch=" + branch), function(){$('#build').chosen();}); +} + +/** + * Flush the branch when switching products. + * + * @param int $productID + * @access public + * @return void + */ +function loadBranches(productID) +{ + $('#branch').remove(); + $('#branch_chosen').remove(); + $.get(createLink('branch', 'ajaxGetBranches', "productID=" + productID + '&oldBranch=0¶ms=active&projectID=' + projectID), function(data) + { + var $product = $('#product'); + var $inputGroup = $product.closest('.input-group'); + $inputGroup.find('.input-group-addon').toggleClass('hidden', !data); + if(data) + { + $inputGroup.append(data); + $inputGroup.find('#branch').attr('onchange', 'loadBuilds()'); + $('#branch').css('width', '120px').chosen(); + } + loadBuilds(); + $inputGroup.fixInputGroup(); + }) +} diff --git a/module/projectrelease/model.php b/module/projectrelease/model.php index a976dbebc6..29ef014a2e 100644 --- a/module/projectrelease/model.php +++ b/module/projectrelease/model.php @@ -23,10 +23,9 @@ class projectreleaseModel extends model */ public function getByID($releaseID, $setImgSize = false) { - $release = $this->dao->select('t1.*, t2.id as buildID, t2.filePath, t2.scmPath, t2.name as buildName, t2.execution, t3.name as productName, t3.type as productType') + $release = $this->dao->select('t1.*, t2.name as productName, t2.type as productType') ->from(TABLE_RELEASE)->alias('t1') - ->leftJoin(TABLE_BUILD)->alias('t2')->on('t1.build = t2.id') - ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id') + ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id') ->where('t1.id')->eq((int)$releaseID) ->orderBy('t1.id DESC') ->fetch(); @@ -34,8 +33,9 @@ class projectreleaseModel extends model $this->loadModel('file'); $release = $this->file->replaceImgURL($release, 'desc'); - $release->files = $this->file->getByObject('release', $releaseID); - if(empty($release->files))$release->files = $this->file->getByObject('build', $release->buildID); + $release->files = $this->file->getByObject('release', $releaseID); + $release->buildInfos = $this->dao->select('id,project,product,name,scmPath,filePath')->from(TABLE_BUILD)->where('id')->in($release->build)->fetchAll('id'); + if(empty($release->files))$release->files = $this->file->getByObject('build', $release->build); if($setImgSize) $release->desc = $this->file->setImgSize($release->desc); return $release; } @@ -51,15 +51,12 @@ class projectreleaseModel extends model */ public function getList($projectID, $type = 'all', $orderBy = 't1.date_desc', $pager = null) { - return $this->dao->select('t1.*, t2.name as productName, t3.id as buildID, t3.name as buildName, t3.execution, t4.name as executionName') - ->from(TABLE_RELEASE)->alias('t1') + return $this->dao->select('t1.*, t2.name as productName')->from(TABLE_RELEASE)->alias('t1') ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id') - ->leftJoin(TABLE_BUILD)->alias('t3')->on('t1.build = t3.id') - ->leftJoin(TABLE_EXECUTION)->alias('t4')->on('t3.execution = t4.id') - ->where('t1.project')->eq((int)$projectID) + ->where('t1.deleted')->eq(0) + ->andWhere("FIND_IN_SET($projectID, t1.project)") ->beginIF($type != 'all' && $type != 'review')->andWhere('t1.status')->eq($type)->fi() ->beginIF($type == 'review')->andWhere("FIND_IN_SET('{$this->app->user->account}', t1.reviewers)")->fi() - ->andWhere('t1.deleted')->eq(0) ->orderBy($orderBy) ->page($pager) ->fetchAll(); @@ -74,8 +71,10 @@ class projectreleaseModel extends model */ public function getLast($projectID) { + $project = (int)$projectID; return $this->dao->select('id, name')->from(TABLE_RELEASE) - ->where('project')->eq((int)$projectID) + ->where('deleted')->eq(0) + ->andWhere("FIND_IN_SET($projectID, project)") ->orderBy('date DESC') ->limit(1) ->fetch(); @@ -92,9 +91,12 @@ class projectreleaseModel extends model { $releases = $this->dao->select('build')->from(TABLE_RELEASE) ->where('deleted')->eq(0) - ->andWhere('project')->eq($projectID) - ->fetchAll('build'); - return array_keys($releases); + ->andWhere("FIND_IN_SET($projectID, project)") + ->fetchAll(); + + $buildIdList = array(); + foreach($releases as $release) $buildIdList = array_merge($buildIdList, explode(',', trim($release->build, ','))); + return $buildIdList; } /** @@ -109,14 +111,13 @@ class projectreleaseModel extends model /* Init vars. */ $releaseID = (int)$releaseID; $oldRelease = $this->dao->select('*')->from(TABLE_RELEASE)->where('id')->eq($releaseID)->fetch(); - $branch = $this->dao->select('branch')->from(TABLE_BUILD)->where('id')->eq((int)$this->post->build)->fetch('branch'); /* Check build if build is required. */ if(strpos($this->config->release->edit->requiredFields, 'build') !== false and $this->post->build == false) return dao::$errors['build'] = sprintf($this->lang->error->notempty, $this->lang->release->build); $release = fixer::input('post')->stripTags($this->config->release->editor->edit['id'], $this->config->allowedTags) - ->add('branch', (int)$branch) ->setDefault('mailto', '') + ->join('build', ',') ->join('mailto', ',') ->setIF(!$this->post->marker, 'marker', 0) ->cleanInt('product') @@ -127,7 +128,7 @@ class projectreleaseModel extends model $this->dao->update(TABLE_RELEASE)->data($release) ->autoCheck() ->batchCheck($this->config->release->edit->requiredFields, 'notempty') - ->check('name', 'unique', "id != '$releaseID' AND product = '{$release->product}' AND branch = '$branch' AND deleted = '0'") + ->check('name', 'unique', "id != '$releaseID' AND product = '{$oldRelease->product}' AND branch = '{$oldRelease->branch}' AND deleted = '0'") ->where('id')->eq((int)$releaseID) ->exec(); if(!dao::isError()) diff --git a/module/projectrelease/view/browse.html.php b/module/projectrelease/view/browse.html.php index 399a5f543a..9764482095 100644 --- a/module/projectrelease/view/browse.html.php +++ b/module/projectrelease/view/browse.html.php @@ -44,7 +44,7 @@

- +
@@ -52,7 +52,6 @@ hasProduct):?> - @@ -65,28 +64,40 @@ + build, ',')); + $buildCount = count($linkedBuilds); + foreach($linkedBuilds as $i => $buildID): + ?> + + + 1 ? "rowspan='$buildCount'" : '';?> - - + hasProduct):?> - + - - hasProduct and !$project->multiple) ? 'data-app=project' : '';?> - + processStatus('release', $release);?> - - - " . $this->loadModel('flow')->getFieldValue($extendField, $release) . "";?> - + + " . $this->loadModel('flow')->getFieldValue($extendField, $release) . "";?> + + + + + + +
release->id;?>release->product;?> executionCommon;?> release->build;?> release->status;?> release->date;?>
id"), sprintf('%03d', $release->id));?> + >id"), sprintf('%03d', $release->id));?>> marker ? " " : ''; echo html::a(inlink('view', "release=$release->id"), $release->name, '', "data-app='$from'") . $flagIcon; ?> productName?> title='productName?>'>productName?> executionName?>execution) ? $release->buildName : html::a($this->createLink('build', 'view', "buildID=$release->buildID"), $release->buildName, '', "$dataApp");?>createLink('build', 'view', "buildID=$buildID"), $buildName, '', "data-app='project'");?> + class='c-status text-center' title=''> date;?>projectrelease->buildOperateMenu($release, 'browse');?> class='text-center'>date;?> class='c-actions'>projectrelease->buildOperateMenu($release, 'browse');?>
createLink('build', 'view', "buildID=$buildID"), $buildName, '', "data-app='project'");?>
diff --git a/module/projectrelease/view/create.html.php b/module/projectrelease/view/create.html.php index 7e12384694..ea809f9afe 100644 --- a/module/projectrelease/view/create.html.php +++ b/module/projectrelease/view/create.html.php @@ -34,22 +34,22 @@
release->build;?> - -
release->product;?>
- + id, "onchange='loadBranches(this.value)' class='form-control chosen'");?> type != 'normal') echo html::select('branch', $branches, $branch, "class='form-control chosen control-branch'");?>
release->build;?> + +
release->date;?>
release->build;?>build, "class='form-control chosen' required"); ?>build, "class='form-control chosen' multiple required"); ?>
release->date;?> parent > 0) echo "{$lang->story->childrenAB}"; - echo html::a($this->createLink('story', 'view', "storyID={$story->id}&version=0¶m=$projectID", '', true), $story->title, '', "data-toggle='modal' data-type='iframe' data-width='90%'"); + echo html::a($this->createLink('story', 'view', "storyID={$story->id}&version=0¶m={$this->session->project}", '', true), $story->title, '', "data-toggle='modal' data-type='iframe' data-width='90%'"); ?> openedBy);?>
@@ -351,8 +351,15 @@
release->build;?> - execution) ? html::a($this->createLink('build', 'view', "buildID=$release->buildID"), $release->buildName, '_blank') : $release->buildName;?> + + buildInfos as $buildID => $buildInfo) + { + $buildHtml[] = html::a($this->createLink('build', 'view', "buildID=$buildID"), $buildInfo->name); + } + echo join(', ', $buildHtml); + ?>
+
- - type != 'normal'):?> - - - + + + builds);?> + + builds as $build):?> - - + - - type != 'normal'):?> - - + + + processStatus('release', $release);?> - - - " . $this->loadModel('flow')->getFieldValue($extendField, $release) . "";?> - + + " . $this->loadModel('flow')->getFieldValue($extendField, $release) . "";?> + + + +
release->id;?> release->name;?>release->build;?>product->branch;?>release->project;?>release->build;?>release->project;?> release->status;?> release->date;?>
id"), sprintf('%03d', $release->id));?> + + id"), sprintf('%03d', $release->id));?> marker ? " " : ''; echo html::a(inlink('view', "release=$release->id"), $release->name, '', "title='$release->name'") . $flagIcon; ?> execution) ? $release->buildName : html::a($this->createLink('build', 'view', "buildID=$release->buildID"), $release->buildName);?>'>branch];?> projectName;?> + type != 'normal'):?> + branchName;?> + + createLink('build', 'view', "buildID=$build->id"), $build->name);?> + projectName;?> + date;?> - release->buildOperateMenu($release, 'browse');?> - date;?>release->buildOperateMenu($release, 'browse');?>
diff --git a/module/release/view/create.html.php b/module/release/view/create.html.php index f4ed3c4469..3b1a30c5e3 100644 --- a/module/release/view/create.html.php +++ b/module/release/view/create.html.php @@ -35,7 +35,7 @@
release->build;?>
release->build;?>build, "class='form-control chosen' required"); ?>build, "class='form-control chosen' required multiple"); ?>
release->date;?>
release->build;?> - project) ? html::a($this->createLink('build', 'view', "buildID=$release->buildID"), $release->buildName, '_blank') : $release->buildName;?> + + builds as $build) + { + $buildDivision = $i == count($release->builds) ? '' : $lang->comma; + echo (($build->project) ? html::a($this->createLink('build', 'view', "buildID=$build->id"), $build->name, '_blank') : $build->name) . $buildDivision; + $i++; + } + ?>
story->title;?> story->status;?> story->closedReason;?>story->comment;?>story->comment;?>
' closedReason != 'duplicate') echo "style='display:none'";?>> - '') + $productStoryList[$story->product][$story->branch], $story->duplicateStory ? $story->duplicateStory : '', "class='form-control' placeholder='{$lang->bug->duplicateTip}'");?> - ' closedReason != 'subdivided') echo "style='display:none'";?>> idAB}'");?>
+ ' closedReason != 'duplicate') echo "style='display:none'";?>> + '') + $productStoryList[$story->product][$story->branch], $story->duplicateStory ? $story->duplicateStory : '', "class='form-control' placeholder='{$lang->bug->duplicateTip}'");?> + diff --git a/module/story/view/linkstories.html.php b/module/story/view/linkstories.html.php index 790be8ea2c..587eb6d437 100644 --- a/module/story/view/linkstories.html.php +++ b/module/story/view/linkstories.html.php @@ -90,9 +90,9 @@ $(function() var output = ''; $('#linkStoriesForm').find('tr.checked').each(function(){ var storyID = $(this).find('td.c-id').find('div.checkbox-primary input').attr('value'); - var storyTitle = "#" + storyID + ' ' + $(this).find('td').eq(3).attr('title'); + var storyTitle = "#" + storyID + ' ' + $(this).find('td').eq(2).attr('title'); var linkStoryField = storyType == 'story' ? 'linkStories' : 'linkRequirements'; - var checkbox = "
  • "; + var checkbox = "
  • "; output += checkbox; }); diff --git a/module/task/control.php b/module/task/control.php index 9bfe93541d..457a2d6734 100755 --- a/module/task/control.php +++ b/module/task/control.php @@ -62,6 +62,7 @@ class task extends control $task = new stdClass(); $task->module = $moduleID; + $task->mode = ''; $task->assignedTo = ''; $task->name = ''; $task->story = $storyID; diff --git a/module/task/js/create.js b/module/task/js/create.js index 538b94c5de..36e63a45ff 100644 --- a/module/task/js/create.js +++ b/module/task/js/create.js @@ -27,6 +27,32 @@ $(function() loadStories(executionID); }) +/** + * Show team menu. + * + * @access public + * @return void + */ +function showTeamMenu() +{ + if($('[name^=multiple]').prop('checked')) + { + $('#assignedTo, #assignedTo_chosen').addClass('hidden'); + $('#assignedTo').next('.picker').addClass('hidden'); + $('.team-group').removeClass('hidden'); + $('.modeBox').removeClass('hidden'); + $('#estimate').attr('readonly', true); + } + else + { + $('#assignedTo, #assignedTo_chosen').removeClass('hidden'); + $('#assignedTo').next('.picker').removeClass('hidden'); + $('.team-group').addClass('hidden'); + $('.modeBox').addClass('hidden'); + $('#estimate').attr('readonly', false); + } + $('#dataPlanGroup').fixInputGroup(); +} /** * Load module, stories and members. * @@ -518,23 +544,7 @@ $(document).ready(function() /* Show team menu. */ $('[name^=multiple]').change(function() { - if($(this).prop('checked')) - { - $('#assignedTo, #assignedTo_chosen').addClass('hidden'); - $('#assignedTo').next('.picker').addClass('hidden'); - $('.team-group').removeClass('hidden'); - $('.modeBox').removeClass('hidden'); - $('#estimate').attr('readonly', true); - } - else - { - $('#assignedTo, #assignedTo_chosen').removeClass('hidden'); - $('#assignedTo').next('.picker').removeClass('hidden'); - $('.team-group').addClass('hidden'); - $('.modeBox').addClass('hidden'); - $('#estimate').attr('readonly', false); - } - $('#dataPlanGroup').fixInputGroup(); + showTeamMenu(); }); $('#showAllModule').change(function() diff --git a/module/task/js/view.js b/module/task/js/view.js index 45c9d15b55..4dc0953f1a 100644 --- a/module/task/js/view.js +++ b/module/task/js/view.js @@ -23,6 +23,7 @@ function ajaxRefresh() { $data = $(data); $('#actionbox ol.histories-list').html($data.find('#actionbox ol.histories-list').html()); + $('#actionbox ol.histories-list form.comment-edit-form').ajaxForm(); $('.side-col').html($data.find('.side-col').html()); if($('#actionbox ol.histories-list #lastComment').length > 0) $('#actionbox ol.histories-list #lastComment').kindeditor(); diff --git a/module/task/model.php b/module/task/model.php index d289ce0c37..bb49e78247 100755 --- a/module/task/model.php +++ b/module/task/model.php @@ -4067,6 +4067,7 @@ class taskModel extends model } } $list .= ''; + if(!empty($execution->division) and $this->app->rawModule == 'project') $list .= ''; $list .= "" . $this->processStatus('task', $task) . ''; $list .= '' . zget($users, $task->assignedTo, '') . ''; $list .= helper::isZeroDate($task->estStarted) ? '' : '' . $task->estStarted . ''; @@ -4140,7 +4141,7 @@ class taskModel extends model $menu .= "
    "; $menu .= $this->buildMenu('task', 'edit', $params, $task, 'view', '', '', 'showinonlybody'); - if(empty($task->team)) $menu .= $this->buildMenu('task', 'create', "projctID={$task->execution}&storyID=0&moduleID=0&taskID=$task->id", $task, 'view', 'copy'); + $menu .= $this->buildMenu('task', 'create', "projctID={$task->execution}&storyID=0&moduleID=0&taskID=$task->id", $task, 'view', 'copy'); $menu .= $this->buildMenu('task', 'delete', "executionID=$task->execution&taskID=$task->id", $task, 'view', 'trash', 'hiddenwin', 'showinonlybody', true); if($task->parent > 0) $menu .= $this->buildMenu('task', 'view', "taskID=$task->parent", $task, 'view', 'chevron-double-up', '', '', '', '', $this->lang->task->parent); diff --git a/module/task/view/create.html.php b/module/task/view/create.html.php index df4bfbc8e1..f66321ccb0 100644 --- a/module/task/view/create.html.php +++ b/module/task/view/create.html.php @@ -43,8 +43,8 @@ foreach(explode(',', $config->task->create->requiredFields) as $field) - type != 'kanban' or $this->config->vision == 'lite') and $execution->multiple):?> - + type != 'kanban' or $this->config->vision == 'lite'):?> + @@ -63,7 +63,7 @@ foreach(explode(',', $config->task->create->requiredFields) as $field) @@ -86,7 +86,7 @@ foreach(explode(',', $config->task->create->requiredFields) as $field) - + type == 'kanban'):?> diff --git a/module/task/view/taskteam.html.php b/module/task/view/taskteam.html.php index 908d1f6720..9a12387608 100644 --- a/module/task/view/taskteam.html.php +++ b/module/task/view/taskteam.html.php @@ -48,6 +48,12 @@ if($task->mode == 'multi' and $app->rawMethod == 'activate') $hourDisabled = fal account);?> @@ -84,7 +91,7 @@ if($task->mode == 'multi' and $app->rawMethod == 'activate') $hourDisabled = fal
    task->execution;?> id, "class='form-control chosen' onchange='loadAll(this.value)' required");?>
    module, "class='form-control chosen' onchange='setStories(this.value, $execution->id)'");?>
    - > + >
    + rawMethod == 'create'):?> +
    + estimate, "class='form-control text-center' placeholder='{$lang->task->estimateAB}'") ?> + +
    +
    estimate, "class='form-control text-center' placeholder='{$lang->task->estimate}'" . ($hourDisabled ? ' readonly' : ''))?> @@ -62,11 +68,12 @@ if($task->mode == 'multi' and $app->rawMethod == 'activate') $hourDisabled = fal
    +
    - mode) and $task->mode == 'linear'):?> + mode) and $task->mode == 'linear'):?> - team)):?> + team) or $app->rawMethod == 'create'):?>
    task->estimateAB}'") ?> @@ -117,7 +124,7 @@ if($task->mode == 'multi' and $app->rawMethod == 'activate') $hourDisabled = fal team) and count($task->team) < 6) ? 6 - count($task->team) : 1;?> status) and $task->status != 'wait' and $task->status != 'doing') $newRowCount = 0;?> -mode) and $task->mode == 'linear') js::set('sortSelector', 'tr.member-wait');?> +mode) and $task->mode == 'linear') js::set('sortSelector', 'tr.member-wait');?> task->error->teamMember);?> status)):?> status);?> @@ -133,10 +140,14 @@ if($task->mode == 'multi' and $app->rawMethod == 'activate') $hourDisabled = fal
    -

    port->exportTemplate;?>

    +

    transfer->exportTemplate;?>

    - + diff --git a/module/port/view/footer.html.php b/module/transfer/view/footer.html.php similarity index 87% rename from module/port/view/footer.html.php rename to module/transfer/view/footer.html.php index 6281292949..4e8f9f0f8c 100644 --- a/module/port/view/footer.html.php +++ b/module/transfer/view/footer.html.php @@ -1,6 +1,6 @@
    port->num;?>transfer->num;?> 'xlsx', 'xls' => 'xls'), 'xlsx', "class='form-control'");?>