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 |