diff --git a/module/execution/control.php b/module/execution/control.php index f934284e97..5df0d4b454 100644 --- a/module/execution/control.php +++ b/module/execution/control.php @@ -1981,6 +1981,18 @@ class execution extends control $userList[$account]['avatar'] = $avatar; } + /* Get execution's product. */ + $productID = 0; + $products = $this->loadModel('product')->getProducts($execution->project); + if($products) $productID = key($products); + + $plans = $this->execution->getPlans($products); + $allPlans = array('' => ''); + if(!empty($plans)) + { + foreach($plans as $plan) $allPlans += $plan; + } + $this->view->title = $this->lang->kanban->view; $this->view->users = $users; $this->view->regions = $kanbanData; @@ -1989,6 +2001,8 @@ class execution extends control $this->view->browseType = $browseType; $this->view->orderBy = $orderBy; $this->view->groupBy = $groupBy; + $this->view->productID = $productID; + $this->view->allPlans = $allPlans; $this->display(); } diff --git a/module/execution/js/kanban.js b/module/execution/js/kanban.js index ccbd589e45..9094d0c3cf 100644 --- a/module/execution/js/kanban.js +++ b/module/execution/js/kanban.js @@ -61,66 +61,6 @@ function createLaneMenu(options) return items; } -/** - * Create card menu. - * - * @param object $options - * @access public - * @return array - */ -function createCardMenu(options) -{ - var card = options.$trigger.closest('.kanban-item').data('item'); - var privs = card.actions; - if(!privs.length) return []; - - var items = []; - if(privs.includes('editCard')) items.push({label: kanbanLang.editCard, icon: 'edit', url: createLink('kanban', 'editCard', 'cardID=' + card.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}}); - if(privs.includes('archiveCard') && kanban.archived == '1') items.push({label: kanbanLang.archiveCard, icon: 'card-archive', url: createLink('kanban', 'archiveCard', 'cardID=' + card.id), attrs: {'target': 'hiddenwin'}}); - if(privs.includes('copyCard')) items.push({label: kanbanLang.copyCard, icon: 'copy', url: createLink('kanban', 'copyCard', 'cardID=' + card.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal'}}); - if(privs.includes('deleteCard')) items.push({label: kanbanLang.deleteCard, icon: 'trash', url: createLink('kanban', 'deleteCard', 'cardID=' + card.id), attrs: {'target': 'hiddenwin'}}); - if(privs.includes('moveCard')) - { - var moveCardItems = []; - var moveColumns = []; - var parentColumns = []; - var regionGroups = regions[options.$trigger.closest('.region').data('id')].groups; - for(let i = 0; i < regionGroups.length ; i ++ ) - { - if(regionGroups[i].id == options.$trigger.closest('.kanban-board').data('id')) - { - moveColumns = regionGroups[i].columns; - break; - } - } - for(let i = moveColumns.length-1 ; i >= 0 ; i -- ) - { - if(moveColumns[i].parent > 0) parentColumns.push(moveColumns[i].parent); - if(moveColumns[i].id == card.column || $.inArray(moveColumns[i].id, parentColumns) >= 0) continue; - moveCardItems.push({label: moveColumns[i].name, onClick: function(){moveCard(card.id, moveColumns[i].id, card.lane, card.kanban, card.region);}}); - } - moveCardItems = moveCardItems.reverse(); - items.push({label: kanbanLang.moveCard, icon: 'move', items: moveCardItems}); - } - if(privs.includes('setCardColor')) - { - var cardColoritems = []; - if(!card.color) color = "#fff"; - for(let i = 0 ; i < colorList.length ; i ++ ) - { - var attr = card.color == colorList[i] ? '' : ''; - var border = i == 0 ? 'border:1px solid #b0b0b0;' : ''; - cardColoritems.push({label: "
" + colorListLang[colorList[i]] + attr , - onClick: function(){setCardColor(card.id, colorList[i], card.kanban, card.region);}, html: true, attrs: {id: 'cardcolormenu'}, className: 'color' + i}); - }; - items.push({label: kanbanLang.cardColor, icon: 'color', items: cardColoritems}); - } - - var bounds = options.$trigger[0].getBoundingClientRect(); - items.$options = {x: bounds.right, y: bounds.top}; - return items; -} - function createColumnMenu(options) { var column = options.$trigger.closest('.kanban-col').data('col'); @@ -130,14 +70,6 @@ function createColumnMenu(options) var items = []; if(privs.includes('setColumn')) items.push({label: kanbanLang.editColumn, icon: 'edit', url: createLink('kanban', 'setColumn', 'columnID=' + column.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal'}}); if(privs.includes('setWIP')) items.push({label: kanbanLang.setWIP, icon: 'alert', url: createLink('kanban', 'setWIP', 'columnID=' + column.id), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width' : '500px'}}); - if(privs.includes('splitColumn')) items.push({label: kanbanLang.splitColumn, icon: 'col-split', url: createLink('kanban', 'splitColumn', 'columnID=' + column.id, '', true), className: 'iframe', attrs: {'data-toggle': 'modal'}}); - if(privs.includes('createColumn')) - { - items.push({label: kanbanLang.createColumnOnLeft, icon: 'col-add-left', url: createLink('kanban', 'createColumn', 'columnID=' + column.id + '&position=left'), className: 'iframe', attrs: {'data-toggle': 'modal'}}); - items.push({label: kanbanLang.createColumnOnRight, icon: 'col-add-right', url: createLink('kanban', 'createColumn', 'columnID=' + column.id + '&position=right'), className: 'iframe', attrs: {'data-toggle': 'modal'}}); - } - if(privs.includes('copyColumn')) items.push({label: kanbanLang.copyColumn, icon: 'copy', url: createLink('kanban', 'copyColumn', 'columnID=' + column.id), className: 'iframe', attrs: {'data-toggle': 'modal'}}); - if(privs.includes('archiveColumn') && kanban.archived == '1' && column.$kanbanData.columns.length > 1) items.push({label: kanbanLang.archiveColumn, icon: 'card-archive', url: createLink('kanban', 'archiveColumn', 'columnID=' + column.id), attrs: {'target': 'hiddenwin'}}); if(privs.includes('deleteColumn') && column.$kanbanData.columns.length > 1) items.push({label: kanbanLang.deleteColumn, icon: 'trash', url: createLink('kanban', 'deleteColumn', 'columnID=' + column.id), attrs: {'target': 'hiddenwin'}}); var bounds = options.$trigger[0].getBoundingClientRect(); @@ -145,6 +77,35 @@ function createColumnMenu(options) return items; } +/** + * Create column create button menu + * @returns {Object[]} + */ +function createColumnCreateMenu(options) +{ + var $col = options.$trigger.closest('.kanban-col'); + var col = $col.data('col'); + var items = []; + + if(col.type == 'backlog') + { + if(priv.canCreateStory) items.push({label: storyLang.create, url: $.createLink('story', 'create', 'productID=' + productID)}); + if(priv.canBatchCreateStory) items.push({label: executionLang.batchCreateStroy, url: $.createLink('story', 'batchcreate', 'productID=' + productID + '&branch=0&moduleID=0&storyID=0&executionID=' + executionID)}); + if(priv.canLinkStory) items.push({label: executionLang.linkStory, url: $.createLink('execution', 'linkStory', 'executionID=' + executionID)}); + if(priv.canLinkStoryByPlane) items.push({label: executionLang.linkStoryByPlan, url: '#linkStoryByPlan', 'attrs' : {'data-toggle': 'modal'}}); + } + else if(col.type == 'unconfirmed') + { + if(priv.canCreateBug) items.push({label: bugLang.create, url: $.createLink('bug', 'create', 'productID=0&moduleID=0&extra=executionID=' + executionID)}); + if(priv.canBatchCreateBug) items.push({label: bugLang.batchCreate, url: $.createLink('bug', 'batchcreate', 'productID=' + productID + '&moduleID=0&executionID=' + executionID)}); + } + else if(col.type == 'wait') + { + if(priv.canCreateTask) items.push({label: taskLang.create, url: $.createLink('task', 'create', 'executionID=' + executionID, '', true), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}}); + if(priv.canBatchCreateTask) items.push({label: taskLang.batchCreate, url: $.createLink('task', 'batchcreate', 'executionID=' + executionID, '', true), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}}); + } + return items; +} /** * Hide kanban action @@ -235,6 +196,7 @@ function renderHeaderCol($column, column, $header, kanbanData) /* Render group header. */ var privs = kanbanData.actions; var columnPrivs = kanbanData.columns[0].actions; + var $actions = $('
'); if(privs.includes('sortGroup')) { @@ -255,26 +217,22 @@ function renderHeaderCol($column, column, $header, kanbanData) var groupID = $column.closest('.kanban-board').data('id'); var laneID = column.$kanbanData.lanes[0].id ? column.$kanbanData.lanes[0].id : 0; var columnID = $column.closest('.kanban-col').data('id'); - var printMoreBtn = (columnPrivs.includes('setColumn') || columnPrivs.includes('setWIP') || columnPrivs.includes('createColumn') || columnPrivs.includes('copyColumn') || columnPrivs.includes('archiveColumn') || columnPrivs.includes('deleteColumn') || columnPrivs.includes('splitColumn')); + var printMoreBtn = (columnPrivs.includes('setColumn') || columnPrivs.includes('setWIP') || columnPrivs.includes('deleteColumn')); /* Render more menu. */ - if(columnPrivs.includes('createCard') || printMoreBtn) + if((column.type == 'backlog' && hasStoryButton) || (column.type == 'wait' && hasTaskButton) || (column.type == 'unconfirmed' && hasBugButton)) { - var addItemBtn = ''; - var moreAction = ''; - - if(!$column.children('.actions').length) $column.append('
'); - var $actions = $column.children('.actions'); - - if(columnPrivs.includes('createCard') && column.parent != -1) - { - var cardUrl = createLink('kanban', 'createCard', 'kanbanID=' + kanbanID + '®ionID=' + regionID + '&groupID=' + groupID + '&laneID=' + laneID + '&columnID=' + columnID); - addItemBtn = ['', '', ''].join(''); - } - - var moreAction = ' '; - $actions.html(addItemBtn + moreAction); + $actions.append([ + '', + '', + '' + ].join('')); } + if(printMoreBtn) + { + $actions.append(' '); + } + $actions.appendTo($column); } /** @@ -307,6 +265,7 @@ function renderLaneName($lane, lane, $kanban, columns, kanban) ].join('')).appendTo($lane); } } + /** * Adjust add button postion in column */ @@ -328,6 +287,7 @@ function adjustAddBtnPosition($kanban) $col.toggleClass('has-scrollbar', items.scrollHeight > items.clientHeight); }); } + /** * Handle kanban action */ @@ -355,6 +315,7 @@ function processMinusBtn() $('#splitTable .btn-plus').hide(); } } + /** * The function for rendering kanban item */ @@ -446,9 +407,9 @@ function renderKanbanItem(item, $item) /* Define menu creators */ window.menuCreators = { - card: createCardMenu, - lane: createLaneMenu, - column: createColumnMenu + lane: createLaneMenu, + column: createColumnMenu, + columnCreate: createColumnCreateMenu }; /** diff --git a/module/execution/view/kanban.html.php b/module/execution/view/kanban.html.php index 2ecc5dc6dd..9b79c075de 100644 --- a/module/execution/view/kanban.html.php +++ b/module/execution/view/kanban.html.php @@ -19,8 +19,13 @@ foreach($regions as $region) $laneCount += $region->laneCount; js::set('regions', $regions); js::set('execution', $execution); +js::set('productID', $productID); js::set('kanbanLang', $lang->kanban); js::set('kanbanlaneLang', $lang->kanbanlane); +js::set('storyLang', $lang->story); +js::set('executionLang', $lang->execution); +js::set('bugLang', $lang->bug); +js::set('taskLang', $lang->task); js::set('kanbancolumnLang', $lang->kanbancolumn); js::set('kanbancardLang', $lang->kanbancard); js::set('executionID', $execution->id); @@ -35,6 +40,33 @@ $canSortRegion = commonModel::hasPriv('kanban', 'sortRegion') && count($region $canEditRegion = commonModel::hasPriv('kanban', 'editRegion'); $canDeleteRegion = commonModel::hasPriv('kanban', 'deleteRegion'); $canCreateLane = commonModel::hasPriv('kanban', 'createLane'); +$canCreateTask = common::hasPriv('task', 'create'); +$canBatchCreateTask = common::hasPriv('task', 'batchCreate'); +$canCreateBug = common::hasPriv('bug', 'create'); +$canBatchCreateBug = common::hasPriv('bug', 'batchCreate'); +$canCreateStory = ($productID and common::hasPriv('story', 'create')); +$canBatchCreateStory = ($productID and common::hasPriv('story', 'batchCreate')); +$canLinkStory = ($productID and common::hasPriv('execution', 'linkStory')); +$canLinkStoryByPlane = ($productID and common::hasPriv('execution', 'importplanstories')); +$hasStoryButton = ($canCreateStory or $canBatchCreateStory or $canLinkStory or $canLinkStoryByPlane); +$hasTaskButton = ($canCreateTask or $canBatchCreateTask); +$hasBugButton = ($canCreateBug or $canBatchCreateBug); + +js::set('priv', + array( + 'canCreateTask' => $canCreateTask, + 'canBatchCreateTask' => $canBatchCreateTask, + 'canCreateBug' => $canCreateBug, + 'canBatchCreateBug' => $canBatchCreateBug, + 'canCreateStory' => $canCreateStory, + 'canBatchCreateStory' => $canBatchCreateStory, + 'canLinkStory' => $canLinkStory, + 'canLinkStoryByPlane' => $canLinkStoryByPlane, + ) +); +js::set('hasStoryButton', $hasStoryButton); +js::set('hasBugButton', $hasBugButton); +js::set('hasTaskButton', $hasTaskButton); ?>
-
-
+ diff --git a/module/kanban/model.php b/module/kanban/model.php index a24ad1b8f9..e17755d2e8 100644 --- a/module/kanban/model.php +++ b/module/kanban/model.php @@ -649,7 +649,7 @@ class kanbanModel extends model $lanes = zget($laneGroup, $group->id, array()); if(!$lanes) continue; - foreach($lanes as $lane) $lane->items = array(); + foreach($lanes as $lane) $lane->items = array(); $group->columns = zget($columnGroup, $group->id, array()); $group->lanes = $lanes; @@ -913,18 +913,10 @@ class kanbanModel extends model */ public function getCardGroupByExecution($executionID, $browseType = 'all', $orderBy = 'id_asc') { - $cards = $this->dao->select("id, title, pri, type, severity, assignedTo, '' as estimate, deadline")->from(TABLE_BUG) - ->where('deleted')->eq(0) - ->andWhere('execution')->eq($executionID) - ->fetchAll('id'); -/* - $cards .= $this->dao->select("id, name, pri, '' as severity, assignedTo, deadline")->from(TABLE_TASK) - ->where('deleted')->eq(0) - ->andWhere('execution')->eq($executionID) - ->fetchAll('id'); - */ - - $planList = $this->loadModel('execution')->getById($executionID); + $cards = array(); + if($browseType == 'all' or $browseType == 'story') $cards['story'] = $this->loadModel('story')->getExecutionStories($executionID, 0, 0, $orderBy); + if($browseType == 'all' or $browseType == 'bug') $cards['bug'] = $this->loadModel('bug')->getExecutionBugs($executionID, 0, 0, '', 0, $orderBy); + if($browseType == 'all' or $browseType == 'task') $cards['task'] = $this->loadModel('execution')->getKanbanTasks($executionID, $orderBy); return $cards; } @@ -1660,12 +1652,12 @@ class kanbanModel extends model /** * Park cards into kanban cell. - * - * @param int $kanbanID - * @param int $laneID - * @param int $colID - * @param string $cards - * @param string $type + * + * @param int $kanbanID + * @param int $laneID + * @param int $colID + * @param string $cards + * @param string $type * @access public * @return void */