diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -28,68 +28,33 @@ $project = $this->getProject(); $this->readRequestState(); - $columns = $this->loadColumns($project); - - // TODO: Expand the checks here if we add the ability - // to hide the Backlog column - if (!$columns) { - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $project, - PhabricatorPolicyCapability::CAN_EDIT); - if (!$can_edit) { - $content = $this->buildNoAccessContent($project); - } else { - $content = $this->buildInitializeContent($project); - } - - if ($content instanceof AphrontResponse) { - return $content; - } - - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorProject::PANEL_WORKBOARD); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Workboard')); - - return $this->newPage() - ->setTitle( - array( - pht('Workboard'), - $project->getName(), - )) - ->setNavigation($nav) - ->setCrumbs($crumbs) - ->appendChild($content); - } $board_uri = $this->getApplicationURI('board/'.$project->getID().'/'); - $engine = id(new ManiphestTaskSearchEngine()) + $search_engine = id(new ManiphestTaskSearchEngine()) ->setViewer($viewer) ->setBaseURI($board_uri) ->setIsBoardView(true); - if ($request->isFormPost()) { - $saved = $engine->buildSavedQueryFromRequest($request); - $engine->saveQuery($saved); + if ($request->isFormPost() && !$request->getBool('initialize')) { + $saved = $search_engine->buildSavedQueryFromRequest($request); + $search_engine->saveQuery($saved); $filter_form = id(new AphrontFormView()) ->setUser($viewer); - $engine->buildSearchForm($filter_form, $saved); - if ($engine->getErrors()) { + $search_engine->buildSearchForm($filter_form, $saved); + if ($search_engine->getErrors()) { return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FULL) ->setTitle(pht('Advanced Filter')) ->appendChild($filter_form->buildLayoutView()) - ->setErrors($engine->getErrors()) + ->setErrors($search_engine->getErrors()) ->setSubmitURI($board_uri) ->addSubmitButton(pht('Apply Filter')) ->addCancelButton($board_uri); } return id(new AphrontRedirectResponse())->setURI( $this->getURIWithState( - $engine->getQueryResultsPageURI($saved->getQueryKey()))); + $search_engine->getQueryResultsPageURI($saved->getQueryKey()))); } $query_key = $request->getURIData('queryKey'); @@ -99,8 +64,8 @@ $this->queryKey = $query_key; $custom_query = null; - if ($engine->isBuiltinQuery($query_key)) { - $saved = $engine->buildSavedQueryFromBuiltin($query_key); + if ($search_engine->isBuiltinQuery($query_key)) { + $saved = $search_engine->buildSavedQueryFromBuiltin($query_key); } else { $saved = id(new PhabricatorSavedQueryQuery()) ->setViewer($viewer) @@ -117,7 +82,7 @@ if ($request->getURIData('filter')) { $filter_form = id(new AphrontFormView()) ->setUser($viewer); - $engine->buildSearchForm($filter_form, $saved); + $search_engine->buildSearchForm($filter_form, $saved); return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FULL) @@ -128,7 +93,7 @@ ->addCancelButton($board_uri); } - $task_query = $engine->buildQueryFromSavedQuery($saved); + $task_query = $search_engine->buildQueryFromSavedQuery($saved); $tasks = $task_query ->withEdgeLogicPHIDs( @@ -140,46 +105,46 @@ ->execute(); $tasks = mpull($tasks, null, 'getPHID'); - if ($tasks) { - $positions = id(new PhabricatorProjectColumnPositionQuery()) - ->setViewer($viewer) - ->withObjectPHIDs(mpull($tasks, 'getPHID')) - ->withColumns($columns) - ->execute(); - $positions = mpull($positions, null, 'getObjectPHID'); - } else { - $positions = array(); - } - $task_map = array(); - foreach ($tasks as $task) { - $task_phid = $task->getPHID(); - if (empty($positions[$task_phid])) { - // This shouldn't normally be possible because we create positions on - // demand, but we might have raced as an object was removed from the - // board. Just drop the task if we don't have a position for it. - continue; - } + $board_phid = $project->getPHID(); - $position = $positions[$task_phid]; - $task_map[$position->getColumnPHID()][] = $task_phid; - } + $layout_engine = id(new PhabricatorBoardLayoutEngine()) + ->setViewer($viewer) + ->setBoardPHIDs(array($board_phid)) + ->setObjectPHIDs(array_keys($tasks)) + ->executeLayout(); - // If we're showing the board in "natural" order, sort columns by their - // column positions. - if ($this->sortKey == PhabricatorProjectColumn::ORDER_NATURAL) { - foreach ($task_map as $column_phid => $task_phids) { - $order = array(); - foreach ($task_phids as $task_phid) { - if (isset($positions[$task_phid])) { - $order[$task_phid] = $positions[$task_phid]->getOrderingKey(); - } else { - $order[$task_phid] = 0; - } - } - asort($order); - $task_map[$column_phid] = array_keys($order); + $columns = $layout_engine->getColumns($board_phid); + if (!$columns) { + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $project, + PhabricatorPolicyCapability::CAN_EDIT); + if (!$can_edit) { + $content = $this->buildNoAccessContent($project); + } else { + $content = $this->buildInitializeContent($project); + } + + if ($content instanceof AphrontResponse) { + return $content; } + + $nav = $this->getProfileMenu(); + $nav->selectFilter(PhabricatorProject::PANEL_WORKBOARD); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Workboard')); + + return $this->newPage() + ->setTitle( + array( + pht('Workboard'), + $project->getName(), + )) + ->setNavigation($nav) + ->setCrumbs($crumbs) + ->appendChild($content); } $task_can_edit_map = id(new PhabricatorPolicyFilter()) @@ -198,7 +163,10 @@ return new Aphront404Response(); } - $batch_task_phids = idx($task_map, $batch_column->getPHID(), array()); + $batch_task_phids = $layout_engine->getColumnObjectPHIDs( + $board_phid, + $batch_column->getPHID()); + foreach ($batch_task_phids as $key => $batch_task_phid) { if (empty($task_can_edit_map[$batch_task_phid])) { unset($batch_task_phids[$key]); @@ -251,9 +219,24 @@ $this->handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); foreach ($columns as $column) { - $task_phids = idx($task_map, $column->getPHID(), array()); + if (!$this->showHidden) { + if ($column->isHidden()) { + continue; + } + } + + $task_phids = $layout_engine->getColumnObjectPHIDs( + $board_phid, + $column->getPHID()); + $column_tasks = array_select_keys($tasks, $task_phids); + // If we aren't using "natural" order, reorder the column by the original + // query order. + if ($this->sortKey != PhabricatorProjectColumn::ORDER_NATURAL) { + $column_tasks = array_select_keys($column_tasks, array_keys($tasks)); + } + $panel = id(new PHUIWorkpanelView()) ->setHeader($column->getDisplayName()) ->setSubHeader($column->getDisplayType()) @@ -322,7 +305,7 @@ $filter_menu = $this->buildFilterMenu( $viewer, $custom_query, - $engine, + $search_engine, $query_key); $manage_menu = $this->buildManageMenu($project, $this->showHidden); @@ -383,25 +366,6 @@ $this->sortKey = $sort_key; } - private function loadColumns(PhabricatorProject $project) { - $viewer = $this->getViewer(); - - $column_query = id(new PhabricatorProjectColumnQuery()) - ->setViewer($viewer) - ->withProjectPHIDs(array($project->getPHID())); - - if (!$this->showHidden) { - $column_query->withStatuses( - array(PhabricatorProjectColumn::STATUS_ACTIVE)); - } - - $columns = $column_query->execute(); - $columns = mpull($columns, null, 'getSequence'); - ksort($columns); - - return $columns; - } - private function buildSortMenu( PhabricatorUser $viewer, $sort_key) { @@ -797,6 +761,7 @@ $form = id(new AphrontFormView()) ->setUser($viewer) + ->addHiddenInput('initialize', 1) ->appendRemarkupInstructions( pht('The workboard for this project has not been created yet.')) ->appendControl($new_selector) diff --git a/src/applications/project/engine/PhabricatorBoardLayoutEngine.php b/src/applications/project/engine/PhabricatorBoardLayoutEngine.php --- a/src/applications/project/engine/PhabricatorBoardLayoutEngine.php +++ b/src/applications/project/engine/PhabricatorBoardLayoutEngine.php @@ -6,7 +6,7 @@ private $boardPHIDs; private $objectPHIDs; private $boards; - private $columnMap; + private $columnMap = array(); private $objectColumnMap = array(); private $boardLayout = array(); @@ -68,6 +68,17 @@ return $this; } + public function getColumns($board_phid) { + $columns = idx($this->boardLayout, $board_phid, array()); + return array_select_keys($this->columnMap, array_keys($columns)); + } + + public function getColumnObjectPHIDs($board_phid, $column_phid) { + $columns = idx($this->boardLayout, $board_phid, array()); + $positions = idx($columns, $column_phid, array()); + return mpull($positions, 'getObjectPHID'); + } + public function getObjectColumns($board_phid, $object_phid) { $board_map = idx($this->objectColumnMap, $board_phid, array()); @@ -342,15 +353,16 @@ $board_phid = $board->getPHID(); $position_groups = mgroup($positions, 'getObjectPHID'); + $layout = array(); foreach ($columns as $column) { + $column_phid = $column->getPHID(); + $layout[$column_phid] = array(); + if ($column->isDefaultColumn()) { - $default_phid = $column->getPHID(); - break; + $default_phid = $column_phid; } } - $layout = array(); - $object_phids = $this->getObjectPHIDs(); foreach ($object_phids as $object_phid) { $positions = idx($position_groups, $object_phid, array());