Differential D15176 Diff 36644 src/applications/project/controller/PhabricatorProjectBoardViewController.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/project/controller/PhabricatorProjectBoardViewController.php
| Show All 22 Lines | public function handleRequest(AphrontRequest $request) { | ||||
| $response = $this->loadProject(); | $response = $this->loadProject(); | ||||
| if ($response) { | if ($response) { | ||||
| return $response; | return $response; | ||||
| } | } | ||||
| $project = $this->getProject(); | $project = $this->getProject(); | ||||
| $this->readRequestState(); | $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().'/'); | $board_uri = $this->getApplicationURI('board/'.$project->getID().'/'); | ||||
| $engine = id(new ManiphestTaskSearchEngine()) | $search_engine = id(new ManiphestTaskSearchEngine()) | ||||
| ->setViewer($viewer) | ->setViewer($viewer) | ||||
| ->setBaseURI($board_uri) | ->setBaseURI($board_uri) | ||||
| ->setIsBoardView(true); | ->setIsBoardView(true); | ||||
| if ($request->isFormPost()) { | if ($request->isFormPost() && !$request->getBool('initialize')) { | ||||
| $saved = $engine->buildSavedQueryFromRequest($request); | $saved = $search_engine->buildSavedQueryFromRequest($request); | ||||
| $engine->saveQuery($saved); | $search_engine->saveQuery($saved); | ||||
| $filter_form = id(new AphrontFormView()) | $filter_form = id(new AphrontFormView()) | ||||
| ->setUser($viewer); | ->setUser($viewer); | ||||
| $engine->buildSearchForm($filter_form, $saved); | $search_engine->buildSearchForm($filter_form, $saved); | ||||
| if ($engine->getErrors()) { | if ($search_engine->getErrors()) { | ||||
| return $this->newDialog() | return $this->newDialog() | ||||
| ->setWidth(AphrontDialogView::WIDTH_FULL) | ->setWidth(AphrontDialogView::WIDTH_FULL) | ||||
| ->setTitle(pht('Advanced Filter')) | ->setTitle(pht('Advanced Filter')) | ||||
| ->appendChild($filter_form->buildLayoutView()) | ->appendChild($filter_form->buildLayoutView()) | ||||
| ->setErrors($engine->getErrors()) | ->setErrors($search_engine->getErrors()) | ||||
| ->setSubmitURI($board_uri) | ->setSubmitURI($board_uri) | ||||
| ->addSubmitButton(pht('Apply Filter')) | ->addSubmitButton(pht('Apply Filter')) | ||||
| ->addCancelButton($board_uri); | ->addCancelButton($board_uri); | ||||
| } | } | ||||
| return id(new AphrontRedirectResponse())->setURI( | return id(new AphrontRedirectResponse())->setURI( | ||||
| $this->getURIWithState( | $this->getURIWithState( | ||||
| $engine->getQueryResultsPageURI($saved->getQueryKey()))); | $search_engine->getQueryResultsPageURI($saved->getQueryKey()))); | ||||
| } | } | ||||
| $query_key = $request->getURIData('queryKey'); | $query_key = $request->getURIData('queryKey'); | ||||
| if (!$query_key) { | if (!$query_key) { | ||||
| $query_key = 'open'; | $query_key = 'open'; | ||||
| } | } | ||||
| $this->queryKey = $query_key; | $this->queryKey = $query_key; | ||||
| $custom_query = null; | $custom_query = null; | ||||
| if ($engine->isBuiltinQuery($query_key)) { | if ($search_engine->isBuiltinQuery($query_key)) { | ||||
| $saved = $engine->buildSavedQueryFromBuiltin($query_key); | $saved = $search_engine->buildSavedQueryFromBuiltin($query_key); | ||||
| } else { | } else { | ||||
| $saved = id(new PhabricatorSavedQueryQuery()) | $saved = id(new PhabricatorSavedQueryQuery()) | ||||
| ->setViewer($viewer) | ->setViewer($viewer) | ||||
| ->withQueryKeys(array($query_key)) | ->withQueryKeys(array($query_key)) | ||||
| ->executeOne(); | ->executeOne(); | ||||
| if (!$saved) { | if (!$saved) { | ||||
| return new Aphront404Response(); | return new Aphront404Response(); | ||||
| } | } | ||||
| $custom_query = $saved; | $custom_query = $saved; | ||||
| } | } | ||||
| if ($request->getURIData('filter')) { | if ($request->getURIData('filter')) { | ||||
| $filter_form = id(new AphrontFormView()) | $filter_form = id(new AphrontFormView()) | ||||
| ->setUser($viewer); | ->setUser($viewer); | ||||
| $engine->buildSearchForm($filter_form, $saved); | $search_engine->buildSearchForm($filter_form, $saved); | ||||
| return $this->newDialog() | return $this->newDialog() | ||||
| ->setWidth(AphrontDialogView::WIDTH_FULL) | ->setWidth(AphrontDialogView::WIDTH_FULL) | ||||
| ->setTitle(pht('Advanced Filter')) | ->setTitle(pht('Advanced Filter')) | ||||
| ->appendChild($filter_form->buildLayoutView()) | ->appendChild($filter_form->buildLayoutView()) | ||||
| ->setSubmitURI($board_uri) | ->setSubmitURI($board_uri) | ||||
| ->addSubmitButton(pht('Apply Filter')) | ->addSubmitButton(pht('Apply Filter')) | ||||
| ->addCancelButton($board_uri); | ->addCancelButton($board_uri); | ||||
| } | } | ||||
| $task_query = $engine->buildQueryFromSavedQuery($saved); | $task_query = $search_engine->buildQueryFromSavedQuery($saved); | ||||
| $tasks = $task_query | $tasks = $task_query | ||||
| ->withEdgeLogicPHIDs( | ->withEdgeLogicPHIDs( | ||||
| PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, | PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, | ||||
| PhabricatorQueryConstraint::OPERATOR_AND, | PhabricatorQueryConstraint::OPERATOR_AND, | ||||
| array($project->getPHID())) | array($project->getPHID())) | ||||
| ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY) | ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY) | ||||
| ->setViewer($viewer) | ->setViewer($viewer) | ||||
| ->execute(); | ->execute(); | ||||
| $tasks = mpull($tasks, null, 'getPHID'); | $tasks = mpull($tasks, null, 'getPHID'); | ||||
| if ($tasks) { | |||||
| $positions = id(new PhabricatorProjectColumnPositionQuery()) | $board_phid = $project->getPHID(); | ||||
| $layout_engine = id(new PhabricatorBoardLayoutEngine()) | |||||
| ->setViewer($viewer) | ->setViewer($viewer) | ||||
| ->withObjectPHIDs(mpull($tasks, 'getPHID')) | ->setBoardPHIDs(array($board_phid)) | ||||
| ->withColumns($columns) | ->setObjectPHIDs(array_keys($tasks)) | ||||
| ->execute(); | ->executeLayout(); | ||||
| $positions = mpull($positions, null, 'getObjectPHID'); | |||||
| $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 { | } else { | ||||
| $positions = array(); | $content = $this->buildInitializeContent($project); | ||||
| } | } | ||||
| $task_map = array(); | if ($content instanceof AphrontResponse) { | ||||
| foreach ($tasks as $task) { | return $content; | ||||
| $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; | |||||
| } | } | ||||
| $position = $positions[$task_phid]; | $nav = $this->getProfileMenu(); | ||||
| $task_map[$position->getColumnPHID()][] = $task_phid; | $nav->selectFilter(PhabricatorProject::PANEL_WORKBOARD); | ||||
| } | |||||
| // If we're showing the board in "natural" order, sort columns by their | $crumbs = $this->buildApplicationCrumbs(); | ||||
| // column positions. | $crumbs->addTextCrumb(pht('Workboard')); | ||||
| if ($this->sortKey == PhabricatorProjectColumn::ORDER_NATURAL) { | |||||
| foreach ($task_map as $column_phid => $task_phids) { | return $this->newPage() | ||||
| $order = array(); | ->setTitle( | ||||
| foreach ($task_phids as $task_phid) { | array( | ||||
| if (isset($positions[$task_phid])) { | pht('Workboard'), | ||||
| $order[$task_phid] = $positions[$task_phid]->getOrderingKey(); | $project->getName(), | ||||
| } else { | )) | ||||
| $order[$task_phid] = 0; | ->setNavigation($nav) | ||||
| } | ->setCrumbs($crumbs) | ||||
| } | ->appendChild($content); | ||||
| asort($order); | |||||
| $task_map[$column_phid] = array_keys($order); | |||||
| } | |||||
| } | } | ||||
| $task_can_edit_map = id(new PhabricatorPolicyFilter()) | $task_can_edit_map = id(new PhabricatorPolicyFilter()) | ||||
| ->setViewer($viewer) | ->setViewer($viewer) | ||||
| ->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT)) | ->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT)) | ||||
| ->apply($tasks); | ->apply($tasks); | ||||
| // If this is a batch edit, select the editable tasks in the chosen column | // If this is a batch edit, select the editable tasks in the chosen column | ||||
| // and ship the user into the batch editor. | // and ship the user into the batch editor. | ||||
| $batch_edit = $request->getStr('batch'); | $batch_edit = $request->getStr('batch'); | ||||
| if ($batch_edit) { | if ($batch_edit) { | ||||
| if ($batch_edit !== self::BATCH_EDIT_ALL) { | if ($batch_edit !== self::BATCH_EDIT_ALL) { | ||||
| $column_id_map = mpull($columns, null, 'getID'); | $column_id_map = mpull($columns, null, 'getID'); | ||||
| $batch_column = idx($column_id_map, $batch_edit); | $batch_column = idx($column_id_map, $batch_edit); | ||||
| if (!$batch_column) { | if (!$batch_column) { | ||||
| return new Aphront404Response(); | 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) { | foreach ($batch_task_phids as $key => $batch_task_phid) { | ||||
| if (empty($task_can_edit_map[$batch_task_phid])) { | if (empty($task_can_edit_map[$batch_task_phid])) { | ||||
| unset($batch_task_phids[$key]); | unset($batch_task_phids[$key]); | ||||
| } | } | ||||
| } | } | ||||
| $batch_tasks = array_select_keys($tasks, $batch_task_phids); | $batch_tasks = array_select_keys($tasks, $batch_task_phids); | ||||
| } else { | } else { | ||||
| Show All 36 Lines | public function handleRequest(AphrontRequest $request) { | ||||
| ); | ); | ||||
| $this->initBehavior( | $this->initBehavior( | ||||
| 'project-boards', | 'project-boards', | ||||
| $behavior_config); | $behavior_config); | ||||
| $this->handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); | $this->handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); | ||||
| foreach ($columns as $column) { | 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); | $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()) | $panel = id(new PHUIWorkpanelView()) | ||||
| ->setHeader($column->getDisplayName()) | ->setHeader($column->getDisplayName()) | ||||
| ->setSubHeader($column->getDisplayType()) | ->setSubHeader($column->getDisplayType()) | ||||
| ->addSigil('workpanel'); | ->addSigil('workpanel'); | ||||
| $header_icon = $column->getHeaderIcon(); | $header_icon = $column->getHeaderIcon(); | ||||
| if ($header_icon) { | if ($header_icon) { | ||||
| $panel->setHeaderIcon($header_icon); | $panel->setHeaderIcon($header_icon); | ||||
| ▲ Show 20 Lines • Show All 52 Lines • ▼ Show 20 Lines | public function handleRequest(AphrontRequest $request) { | ||||
| $sort_menu = $this->buildSortMenu( | $sort_menu = $this->buildSortMenu( | ||||
| $viewer, | $viewer, | ||||
| $this->sortKey); | $this->sortKey); | ||||
| $filter_menu = $this->buildFilterMenu( | $filter_menu = $this->buildFilterMenu( | ||||
| $viewer, | $viewer, | ||||
| $custom_query, | $custom_query, | ||||
| $engine, | $search_engine, | ||||
| $query_key); | $query_key); | ||||
| $manage_menu = $this->buildManageMenu($project, $this->showHidden); | $manage_menu = $this->buildManageMenu($project, $this->showHidden); | ||||
| $header_link = phutil_tag( | $header_link = phutil_tag( | ||||
| 'a', | 'a', | ||||
| array( | array( | ||||
| 'href' => $this->getApplicationURI('profile/'.$project->getID().'/'), | 'href' => $this->getApplicationURI('profile/'.$project->getID().'/'), | ||||
| ▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | switch ($sort_key) { | ||||
| break; | break; | ||||
| default: | default: | ||||
| $sort_key = PhabricatorProjectColumn::DEFAULT_ORDER; | $sort_key = PhabricatorProjectColumn::DEFAULT_ORDER; | ||||
| break; | break; | ||||
| } | } | ||||
| $this->sortKey = $sort_key; | $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( | private function buildSortMenu( | ||||
| PhabricatorUser $viewer, | PhabricatorUser $viewer, | ||||
| $sort_key) { | $sort_key) { | ||||
| $sort_icon = id(new PHUIIconView()) | $sort_icon = id(new PHUIIconView()) | ||||
| ->setIcon('fa-sort-amount-asc bluegrey'); | ->setIcon('fa-sort-amount-asc bluegrey'); | ||||
| $named = array( | $named = array( | ||||
| ▲ Show 20 Lines • Show All 379 Lines • ▼ Show 20 Lines | $default_checkbox = id(new AphrontFormCheckboxControl()) | ||||
| ->addCheckbox( | ->addCheckbox( | ||||
| 'default', | 'default', | ||||
| 1, | 1, | ||||
| pht('Make the workboard the default view for this project.'), | pht('Make the workboard the default view for this project.'), | ||||
| true); | true); | ||||
| $form = id(new AphrontFormView()) | $form = id(new AphrontFormView()) | ||||
| ->setUser($viewer) | ->setUser($viewer) | ||||
| ->addHiddenInput('initialize', 1) | |||||
| ->appendRemarkupInstructions( | ->appendRemarkupInstructions( | ||||
| pht('The workboard for this project has not been created yet.')) | pht('The workboard for this project has not been created yet.')) | ||||
| ->appendControl($new_selector) | ->appendControl($new_selector) | ||||
| ->appendControl($default_checkbox) | ->appendControl($default_checkbox) | ||||
| ->appendControl( | ->appendControl( | ||||
| id(new AphrontFormSubmitControl()) | id(new AphrontFormSubmitControl()) | ||||
| ->addCancelButton($profile_uri) | ->addCancelButton($profile_uri) | ||||
| ->setValue(pht('Create Workboard'))); | ->setValue(pht('Create Workboard'))); | ||||
| Show All 26 Lines | |||||